Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit animation of NSView using core animation

I'm trying to slide in a NSView using core animation. I think I need to use explicit animation rather than relying on something like [[view animator] setFrame:newFrame]. This is mainly because I need to set the animation delegate in order to take action after the animation is finished.

I have it working just fine using the animator, but as I said, I need to be notified when the animation finishes. My code currently looks like:

// Animate the controlView
NSRect viewRect = [controlView frame];
NSPoint startingPoint = viewRect.origin;
NSPoint endingPoint = startingPoint;
endingPoint.x += viewRect.size.width;
[[controlView layer] setPosition:NSPointToCGPoint(endingPoint)];

CABasicAnimation *controlPosAnim = [CABasicAnimation animationWithKeyPath:@"position"];
[controlPosAnim setFromValue:[NSValue valueWithPoint:startingPoint]];
[controlPosAnim setToValue:[NSValue valueWithPoint:endingPoint]];
[controlPosAnim setDelegate:self];
[[controlView layer] addAnimation:controlPosAnim forKey:@"controlViewPosition"];

This visually works (and I get notified at the end) but it looks like the actual controlView doesn't get moved. If I cause the window to refresh, the controlView disappears. I tried replacing

[[controlView layer] setPosition:NSPointToCGPoint(endingPoint)];

with

[controlView setFrame:newFrame];

and that does cause the view (and layer) to move, but it is corrupting something such that my app dies with a seg fault soon afterwards.

Most of the examples of explicit animation seem to only be moving a CALayer. There must be a way to moving the NSView and also being able to set a delegate. Any help would be appreciated.

like image 575
btschumy Avatar asked May 19 '11 20:05

btschumy


1 Answers

Changes made to views take effect at the end of the current run loop. The same goes for any animations applied to layers.

If you animate a view's layer, the view itself is unaffected which is why the view appears to jump back to its original position when the animation completes.

With these two things in mind, you can get the effect you want by setting the view's frame to what you want it to be when the animation is done and then adding an explicit animation to the view's layer.

When the animation begins, it moves the view to the starting position, animates it to the end position and when the animation is done, the view has the frame you specified.

- (IBAction)animateTheView:(id)sender
{
    // Calculate start and end points.  
    NSPoint startPoint = theView.frame.origin;
    NSPoint endPoint = <Some other point>;    

    // We can set the frame here because the changes we make aren't actually
    // visible until this pass through the run loop is done.
    // Furthermore, this change to the view's frame won't be visible until
    // after the animation below is finished.
    NSRect frame = theView.frame;
    frame.origin = endPoint;
    theView.frame = frame;

    // Add explicit animation from start point to end point.
    // Again, the animation doesn't start immediately. It starts when this
    // pass through the run loop is done.
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    [animation setFromValue:[NSValue valueWithPoint:startPoint]];
    [animation setToValue:[NSValue valueWithPoint:endPoint]];
    // Set any other properties you want, such as the delegate.
    [theView.layer addAnimation:animation forKey:@"position"];
}

Of course, for this code to work you need to make sure both your view and its superview have layers. If the superview doesn't have a layer, you'll get corrupted graphics.

like image 99
Colin Avatar answered Nov 08 '22 17:11

Colin