Non-dirty view gets redrawn - objective-c

I do have a strange problem. I programmed a a dockview.
It's basically a NSView with three vertical subviews inside. So I have a left dock, a middle view and a right dock. Both docks are collapsable. The middle view gets resized while collapsing.
I'm using CoreAnimation to animate the collapsing process. This works almost correct.
If I uncollapse the left dock, the right docks drawRect method gets called although it's not dirty.
If I deactivate the animator and set the new frame size directly, the right dock not gets redrawn while uncollapsing so I assume the calculation of the frames is correct.
Here is my code for collapsing the docks:
- (void) mouseDown:(NSEvent *)event
{
// Exit method if already animating a dock
if(is_animating)
{
return;
}
NSPoint firstPoint = [self convertPoint:[event locationInWindow] fromView:nil];
unsigned long divider = [self mousePointInCollapseRect:firstPoint];
// If not clicked on a divider don't animate anything
if(divider == NSNotFound)
{
return;
}
NSView* main_view = [[self subviews] objectAtIndex:VIEW_MIDDLE];
NSView* collapse_view = [[self subviews] objectAtIndex:(divider == 0)? VIEW_LEFT : VIEW_RIGHT];
NSRect cframe = [collapse_view frame]; // New collapsable frame
NSRect mframe = [main_view frame]; // New main frame
if([self isSubviewCollapsed:collapse_view] == NO)
{
if(divider == 0)
{
cframe.size.width = 0.0;
mframe.origin.x -= (dock_width_left + DIVIDER_THICKNESS);
mframe.size.width += (dock_width_left + DIVIDER_THICKNESS);
}
else
{
mframe.size.width += (collapse_view.frame.size.width + DIVIDER_THICKNESS);
cframe.size.width = 0.0;
cframe.origin.x = mframe.origin.x + mframe.size.width;
}
// Turn off autoresizesSubviews on the collapsible subview
[collapse_view setAutoresizesSubviews:NO];
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:(ANIMATION_DURATION)];
[[collapse_view animator] setFrame:cframe];
[[main_view animator] setFrame:mframe];
[NSAnimationContext endGrouping];
}
else
{
// Uncollapse dock
if(divider == 0)
{
// Left dock
cframe.size.width = dock_width_left;
mframe.origin.x = (COLLAPSEBAR_THICKNESS + dock_width_left + DIVIDER_THICKNESS);
mframe.size.width -= (dock_width_left + DIVIDER_THICKNESS);
}
else
{
// Right dock
mframe.size.width -= (dock_width_right + DIVIDER_THICKNESS);
cframe.size.width = dock_width_right;
cframe.origin.x = mframe.origin.x + mframe.size.width + DIVIDER_THICKNESS;
}
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:ANIMATION_DURATION];
[[main_view animator] setFrame:mframe];
[[collapse_view animator] setFrame:cframe];
[NSAnimationContext endGrouping];
//Restore Autoresizing on subview
[self performSelector:#selector(restoreAutoResizingOfSubview:) withObject:collapse_view afterDelay:ANIMATION_DURATION + 0.05];
}
is_animating = YES;
[self performSelector:#selector(animationEnded) withObject:nil afterDelay:ANIMATION_DURATION + 0.05];
}
So why gets a non-dirty view redrawn?

Related

NSToolbar with custom views

Hi so I've got a problem that i can't really figure out what the cause is. I made an app so far that has a NSToolbar that has custom views in it so that you can switch between views on the toolbar (just like safari's preference window toolbar views). I was using CCodings tutorial on youtube but i have the problem of the views moving by themselves, for example i click on view 1 and when i click on view 2 view one changes and the view moves up so that you can only see half of when i put on the view, I'm using Xcode 6 and OS X 10.10 so i don't know if that would be one cause but i thank you in advance if you can help me, heres the MainWindowController.m file:
Also the views should be setup correctly
-(NSRect)newFrameForNewContentView:(NSView*)view {
NSWindow *window = [self window];
NSRect newFrameRect = [window frameRectForContentRect:[view frame]];
NSRect oldFrameRect = [window frame];
NSSize newSize = newFrameRect.size;
NSSize oldSize = oldFrameRect.size;
NSRect frame = [window frame];
frame.size = newSize;
frame.origin.y -= (newSize.height - oldSize.height);
return frame;
}
-(NSView *)viewForTag:(int)tag {
NSView *view = nil;
switch (tag) {
case 0:
view = smallView;
break;
case 1:
view = mediumView;
break;
case 2: default:
view = largeView;
break;
}
return view;
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)item {
if ([item tag] == currentViewTag) return NO;
else return YES;
}
-(void)awakeFromNib {
[[self window] setContentSize:[smallView frame].size];
[[[self window] contentView] addSubview:smallView];
[[[self window] contentView] setWantsLayer:YES];
}
-(IBAction)switchView:(id)sender {
int tag = [sender tag];
NSView *view = [self viewForTag:tag];
NSView *previousView = [self viewForTag:currentViewTag];
currentViewTag = tag;
NSRect newFrame = [self newFrameForNewContentView:view];
[NSAnimationContext beginGrouping];
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask)
[[NSAnimationContext currentContext] setDuration:1.0];
[[[[self window] contentView] animator] replaceSubview:previousView with:view];
[[[self window] animator] setFrame:newFrame display:YES];
[NSAnimationContext endGrouping];
}
Seems that you also need to change NSWidnow frame apart from contenView.
Also as for me for switching views by click events its better to use NSTabView.

Switching between custom views using Toolbar

I have a single MainMenu.xib file and I have a window by the name "Preferences"
Also I have four different Custom Views with different Heights. and a Toolbar on that window.
I have linked every toolbar item to the -(void) switchViews:(id)sender; function. The views are linked and are showing up and are also changing but the problem is that if I change from one view to another and then back or to another, the content i.e. buttons, labels, checkboxes on the views keep moving upwards, the window still keeps on changing to right heights, but if I switch enough times the content would disappear from the view.. Any kind of help would be useful.
http://imgur.com/bV3NHa0,zgJCacA
http://imgur.com/bV3NHa0,zgJCacA#1
// FOR CHANGING VIEWS IN THE PREFERENCES WINDOW
- (IBAction)switchView:(id)sender {
long int tag = [sender tag];
NSView *view = [self viewForTag:tag];
NSView *previousView = [self viewForTag:currentViewTag];
currentViewTag = tag;
NSRect newFrame = [self newFrameForNewContentView:view];
[NSAnimationContext beginGrouping];
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
[[NSAnimationContext currentContext] setDuration:1.0];
}
[[[[self preferences] contentView] animator] replaceSubview:previousView with:view];
[[[self preferences] animator] setFrame:newFrame display:YES];
[NSAnimationContext endGrouping];
}
- (NSView *)viewForTag:(long int)tag {
NSView *view = nil;
switch (tag) {
case 0:
view = generalView;
break;
case 1:
view = timesView;
break;
case 2:
view = menubarView;
break;
case 3: default: view = aboutView;
break;
}
return view;
}
- (NSRect)newFrameForNewContentView:(NSView *)view {
NSWindow *window = [self preferences];
NSRect newFrameRect = [window frameRectForContentRect:[view frame]];
NSRect oldFrameRect = [window frame];
NSSize newSize = newFrameRect.size;
NSSize oldSize = oldFrameRect.size;
NSRect frame = [window frame];
frame.size = newSize;
frame.origin.y -= (newSize.height - oldSize.height);
return frame;
}
with the problem you're describing and glancing at your code i would start to suspect this line...
frame.origin.y -= (newSize.height - oldSize.height);

Bounce UIImageView back when dragged off screen

What I need is when a UIImageView is dragged off of the screen it to bounce back when it gets let go. I have it working in the left and top sides this is what I am doing.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (!CGRectContainsPoint(self.view.frame, imageView.frame.origin)){
CGFloat newX = 0.0f;
CGFloat newY = 0.0f;
// If off screen upper and left
if (imageView.frame.origin.x < 0.0f){
CGFloat negX = imageView.frame.origin.x * -1;
newX = negX;
}else{
newX = imageView.frame.origin.x;
}
if (imageView.frame.origin.y < 0.0f){
CGFloat negY = imageView.frame.origin.y * -1;
newY = negY;
}else{
newY = imageView.frame.origin.y;
}
CGRect newPoint = CGRectMake(newX, newY, imageView.frame.size.width, imageView.frame.size.height);
[UIView beginAnimations:#"BounceAnimations" context:nil];
[UIView setAnimationDuration:.5];
[letterOutOfBounds play];
[imageView setFrame:newPoint];
[UIView commitAnimations];
}
}
}
So I would like to achieve the same thing for the right and bottom sides. But I have been stuck at this for awhile. Any Ideas?
How about something like the following?
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UIImageView *imageView = nil;
BOOL moved = NO;
CGRect newPoint = imageView.frame;
// If off screen left
if (newPoint.origin.x < 0.0f){
newPoint.origin.x *= -1.0;
moved = YES;
}
// if off screen up
if (newPoint.origin.y < 0.0f){
newPoint.origin.y *= -1.0;
moved = YES;
}
// if off screen right
CGFloat howFarOffRight = (newPoint.origin.x + newPoint.size.width) - imageView.superview.frame.size.width;
if (howFarOffRight > 0.0)
{
newPoint.origin.x -= howFarOffRight * 2;
moved = YES;
}
// if off screen bottom
CGFloat howFarOffBottom = (newPoint.origin.y + newPoint.size.height) - imageView.superview.frame.size.height;
if (howFarOffBottom > 0.0)
{
newPoint.origin.y -= howFarOffBottom * 2;
moved = YES;
}
if (moved)
{
[UIView beginAnimations:#"BounceAnimations" context:nil];
[UIView setAnimationDuration:.5];
[letterOutOfBounds play];
[imageView setFrame:newPoint];
[UIView commitAnimations];
}
}
As I read your code, the logic of "if off the left side, move it back on to the view by the same distance it was off the screen." To be honest, that doesn't quite make sense to me (why, when bouncing back, does the coordinate depend upon how far off the screen it was), but I've tried to honor that in the "off screen right" and "off screen bottom" logic. Obviously my logic is using the superview of imageView to determine the width of the containing view, but if that's not appropriate, replace it with whatever is.
Edit:
I personally do this stuff with gesture recognizers, such as:
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.imageView addGestureRecognizer:pan];
self.imageView.userInteractionEnabled = YES;
Thus, a gesture recognizer to animate moving the image back would be:
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static CGRect originalFrame; // you could make this an ivar if you want, but just for demonstration purposes
if (gesture.state == UIGestureRecognizerStateBegan)
{
originalFrame = self.imageView.frame;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
CGPoint translate = [gesture translationInView:gesture.view];
CGRect newFrame = originalFrame;
newFrame.origin.x += translate.x;
newFrame.origin.y += translate.y;
gesture.view.frame = newFrame;
}
else if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled)
{
CGRect newFrame = gesture.view.frame;
newFrame.origin.x = fmaxf(newFrame.origin.x, 0.0);
newFrame.origin.x = fminf(newFrame.origin.x, gesture.view.superview.bounds.size.width - newFrame.size.width);
newFrame.origin.y = fmaxf(newFrame.origin.y, 0.0);
newFrame.origin.y = fminf(newFrame.origin.y, gesture.view.superview.bounds.size.height - newFrame.size.height);
// animate how ever you want ... I generally just do animateWithDuration
[UIView animateWithDuration:0.5 animations:^{
gesture.view.frame = newFrame;
}];
}
}
Or, if you want a gesture recognizer that just prevents the dragging of the image off the screen in the first place, it would be:
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static CGRect originalFrame;
if (gesture.state == UIGestureRecognizerStateBegan)
{
originalFrame = self.imageView.frame;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
CGPoint translate = [gesture translationInView:gesture.view];
CGRect newFrame = originalFrame;
newFrame.origin.x += translate.x;
newFrame.origin.x = fmaxf(newFrame.origin.x, 0.0);
newFrame.origin.x = fminf(newFrame.origin.x, gesture.view.superview.bounds.size.width - newFrame.size.width);
newFrame.origin.y += translate.y;
newFrame.origin.y = fmaxf(newFrame.origin.y, 0.0);
newFrame.origin.y = fminf(newFrame.origin.y, gesture.view.superview.bounds.size.height - newFrame.size.height);
gesture.view.frame = newFrame;
}
}
By the way, in iOS 7, you can give the animation of the image view back to its original location a little bounciness by using the new animationWithDuration with the usingSpringWithDampening and initialSpringVelocity parameters:
[UIView animateWithDuration:1.0
delay:0.0
usingSpringWithDamping:0.3
initialSpringVelocity:0.1
options:0
animations:^{
// set the new `frame` (or update the constraint constant values that
// will dictate the `frame` and call `layoutViewsIfNeeded`)
}
completion:nil];
Alternatively, in iOS7, you can also use UIKit Dynamics to add a UISnapBehavior:
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.animator.delegate = self;
UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:self.viewToAnimate snapToPoint:CGPointMake(self.viewToAnimate.center.x, self.view.frame.size.height - 50)];
// optionally, you can control how much bouncing happens when it finishes, e.g., for a lot of bouncing:
//
// snap.damping = 0.2;
// you can also slow down the snap by adding some resistance
//
// UIDynamicItemBehavior *resistance = [[UIDynamicItemBehavior alloc] initWithItems:#[self.viewToAnimate]];
// resistance.resistance = 20.0;
// resistance.angularResistance = 200.0;
// [self.animator addBehavior:resistance];
[self.animator addBehavior:snap];
I think the easiest way is to check whether your imageView has gone out of your self.view.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (!CGRectContainsRect(self.view.frame, imageView.frame)){
// Your animation to bounce.
}
}

Animate NSSplitview and window size

What I'm trying to achieve is that when the user press a button the window grows and the extra space is taken by a panel of NSSplitview that uncollapses and grows to fill the space.
I can easily animate the window resize and the splitview growing independently but when I try to put the two animation together one inevitably happens before the other or by steps. First the window partly resize, then the splitview resize follow, then the window finish resizing and finally the splitview finishes as well. Any ides on why that might be happening?
Here is the code I use:
- (IBAction)helpButtonPressed:(id)sender
{
if ([sender isKindOfClass:[NSMenuItem class]]) [sender setState:![sender state]];
NSWindow *window = [[[self windowControllers] objectAtIndex:0] window];
NSRect oldFrame = [window frame];
CGFloat windowWidthAdd;
if ([sender state]) windowWidthAdd = HELP_WIDTH; else windowWidthAdd = -HELP_WIDTH;
NSRect newFrame = NSMakeRect(oldFrame.origin.x, oldFrame.origin.y,oldFrame.size.width+windowWidthAdd, oldFrame.size.height);
[[NSAnimationContext currentContext] setDuration:0.3f];
[NSAnimationContext beginGrouping];
[[window animator] setFrame:newFrame display:YES];
if ([sender state]) [self uncollapseRightView]; else [self collapseRightView];
[NSAnimationContext endGrouping];
}
-(void)collapseRightView
{
NSView *right = [[self.splitView subviews] objectAtIndex:1];
NSView *left = [[self.splitView subviews] objectAtIndex:0];
NSRect leftFrame = [left frame];
NSRect overallFrame = [self.splitView frame];
[right setHidden:YES];
[[left animator] setFrameSize:NSMakeSize(overallFrame.size.width,leftFrame.size.height)];
}
-(void)uncollapseRightView
{
NSView *left = [[self.splitView subviews] objectAtIndex:0];
NSView *right = [[self.splitView subviews] objectAtIndex:1];
[right setHidden:NO];
CGFloat dividerThickness = [self.splitView dividerThickness];
// get the different frames
NSRect leftFrame = [left frame];
// Adjust left frame size
leftFrame.size.width = (leftFrame.size.width-HELP_WIDTH-dividerThickness);
NSRect rightFrame = [right frame];
rightFrame.origin.x = leftFrame.size.width + dividerThickness;
rightFrame.size.width = HELP_WIDTH;
[[left animator] setFrameSize:leftFrame.size];
[[right animator] setFrame:rightFrame];
}
If you are looking a little closer at the class reference of NSSplitView, you will recognize it conforms to NSAnimatablePropertyContainer. That means NSSplitView will provide you with an "animating proxy of itself". If you call animator on your NSSplitView you'll get this proxy, on which you should be able to change properties in an animated fashion.
For adjusting the animation duration and timing function, use NSAnimationContext the same way you already did.
Last but not least: Did you recognize NSSplitView's minPossiblePositionOfDividerAtIndex: and maxPossiblePositionOfDividerAtIndex:?

NSTextView, appending text and smooth scrolling

I am trying to implement smooth scrolling in a chat history view I implemented, however if the content I append is big enough the smooth scroll will only scroll for a few lines.
My first guess was that the view did not redraw itself yet.. not the case, even when forcing immediate drawing with -display it still breaks.
- (void)scrollAnimated:(BOOL)animated
{
if( animated )
{
NSClipView *clipView = [[_chatHistoryView enclosingScrollView] contentView];
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.100f];
NSPoint constrainedPoint = [clipView constrainScrollPoint:NSMakePoint(0, CGFLOAT_MAX)];
[[clipView animator] setBoundsOrigin:constrainedPoint];
[NSAnimationContext endGrouping];
}
else
{
NSRange range;
range.location = [[_chatHistoryView textStorage] length];
range.length = 1;
[_chatHistoryView scrollRangeToVisible:range];
}
}
What am I doing wrong?
This may help you...
- (void)maybeAutoscrollForThumb:(ThumbImageView *)thumb {
autoscrollDistance = 0;
// only autoscroll if the thumb is overlapping the thumbScrollView
if (CGRectIntersectsRect([thumb frame], [thumbScrollView bounds])) {
CGPoint touchLocation = [thumb convertPoint:[thumb touchLocation] toView:thumbScrollView];
float distanceFromLeftEdge = touchLocation.x - CGRectGetMinX([thumbScrollView bounds]);
float distanceFromRightEdge = CGRectGetMaxX([thumbScrollView bounds]) - touchLocation.x;
if (distanceFromLeftEdge < AUTOSCROLL_THRESHOLD) {
autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromLeftEdge] * -1; // if scrolling left, distance is negative
} else if (distanceFromRightEdge < AUTOSCROLL_THRESHOLD) {
autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromRightEdge];
}
}
// if no autoscrolling, stop and clear timer
if (autoscrollDistance == 0) {
[autoscrollTimer invalidate];
autoscrollTimer = nil;
}
// otherwise create and start timer (if we don't already have a timer going)
else if (autoscrollTimer == nil) {
autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0)
target:self
selector:#selector(autoscrollTimerFired:)
userInfo:thumb
repeats:YES];
}
}