The Differences Between Window, View, and Layer Animation
The idea behind animation in windows, views, and layers is the same; however, the implementation differs. In this section, we discuss one of the most common animations you will likely want to implement—frame resizing.
Since the first version of Mac OS X, the ability to animate a window's frame has been available to developers in the method -(void)setFrame:(NSRect)windowFrame display:(BOOL)displayViews animate:(BOOL)performAnimation. The first parameter is the new frame you are animating to. The second parameter tells the window to call –displayIfNeeded on all of its subviews, and the third parameter tells the window to animate the transition from its current frame to the frame specified in the first parameter. If this last parameter is set to NO, the change to the new frame happens immediately rather than progressively with animation.
With this built-in window frame resizing capability, why would you need to use Core Animation for changing a window's frame? The answer is, simply, you don't. For many cases when resizing, you can use the built-in functionality and you probably should. There may be times, however, when you want more control over animating windows. Keep several things in mind when doing so. NSWindow has an animator proxy just like NSView. When you call the animator, it animates the parameter you specified, but the parameter is the catch. If you want to move the window to a different position on the screen, for instance, you can either call - (void)setFrame:(NSRect)windowFrame display:(BOOL)displayViews (notice the missing third parameter) on the animator proxy object, or you can add an animation to the animations dictionary of the window itself.
First, let's look at how to use the animator proxy. Take a look at the following.
[[window animator] setFrame:newFrame display:YES];
This makes it simple to animate the frame.
By default, the animation plays back over the course of 0.25 seconds. If you want to change the duration, use an NSAnimationContext object, which is the NSView/NSWindow counterpart to the CATransaction. If we wrap the call to –setFrame in an NSAnimationContext, the animation runs at the duration we specify. Listing 3-1 demonstrates how to do this.
Listing 3-1. Wrap Frame Change in an NSAnimationContext
[NSAnimationContext beginGrouping]; [[NSAnimationContext currentContext] setDuration:5.0f]; [[window animator] setFrame:newFrame display:YES]; [NSAnimationContext endGrouping];
This causes the frame to change over the course of 5 seconds rather than the default of 0.25 seconds. As you see in the next section, this grouping mechanism is also what you use when you want to change the duration of an animation for an NSView.
Basic animation using Core Animation can also be used on windows and views, but there is a slight difference in how the animation is set up. As an alternative to calling –setFrame on the window animator proxy, we can create a CABasicAnimation and animate the frame property. Take a look at Listing 3-2 to see how to create, add, and run a basic animation on a window.
Listing 3-2. Adding an Animation to the Window Animations Dictionary
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"frame"]; [animation setFromValue:[NSValue valueWithRect:oldFrame]]; [animation setToValue:[NSValue valueWithRect:newFrame]]; [animation setDuration:5.0f]; [window setAnimations:[NSDictionary animation forKey:@"frame"]]; [[window animator] setFrame:newFrame display:YES];
The visual effect is identical to what you see occur when running the code in Listing 3-1.
Views can be resized the same as windows can, but the keypath you use differs. You can call -setFrame on a view using the same code you used for a window, as shown in Listing 3-3.
Listing 3-3. Animate View Frame Change in an NSAnimationContext
[NSAnimationContext beginGrouping]; [[NSAnimationContext currentContext] setDuration:5.0f]; [[view animator] setFrame:newFrame display:YES]; [NSAnimationContext endGrouping];
The only difference between this code and the code in Listing 3-1 is the object we're calling –setFrame on—a view in this case.
If you want to use explicit animation, instead of animating the frame, animate the frameOrigin and the frameSize. Listing 3-4 shows how to animate both of these properties.
Listing 3-4. Explicitly Animating Frame Origin and Size
CABasicAnimation *originAnimation = [CABasicAnimation animationWithKeyPath:@"frameOrigin"]; [originAnimation setFromValue:[NSValue valueWithPoint:oldImageFrame.origin]]; [originAnimation setToValue:[NSValue valueWithPoint:newFrame.origin]]; [originAnimation setDuration:5.0]; CABasicAnimation *sizeAnimation = [CABasicAnimation animationWithKeyPath:@"frameSize"]; [sizeAnimation setFromValue: [NSValue valueWithSize:oldImageFrame.size]]; [sizeAnimation setToValue:[NSValue valueWithSize:newFrame.size]]; [sizeAnimation setDuration:5.0]; [[view animator] setAnimations:[NSDictionary dictionaryWithObjectsAndKeys:originAnimation, @"frameOrigin", sizeAnimation, @"frameSize", nil]]; [[view animator] setFrame:newFrame];
Animating a layer's frame is a bit different from doing the same in windows and views. There is no animator proxy available in a CALayer object, but rather animation is always used when you make an explicit change to a property. In fact, if you don't want animation used, you have to explicitly turn animation off. Listing 3-5 demonstrates how to do this.
Listing 3-5. Explicitly Disabling Layer Animation
[CATransaction begin] [CATransaction setValue:[NSNumber numberWithBool:YES] forKey: kCATransactionDisableActions] [layer setBounds:bounds]; [CATransaction commit];
The CATransaction class is the Core Animation analogue to AppKit's NSAnimationContext object we used in Listing 3-2 and 3-4 for windows and views. Just like NSAnimationContext, CATransaction enables us to set the animation duration. Listing 3-6 demonstrates how to do this.
Listing 3-6. Setting Animation Duration in a Layer
[CATransaction begin] [CATransaction setValue:[NSNumber numberWithFloat:5.0f] forKey: kCATransactionAnimationDuration] [layer setBounds:bounds]; [CATransaction commit];
As you might suspect, we can also animate properties of a layer explicitly. To achieve the exact same effect as we did with the code in Listing 3-6, we can instead use the code in Listing 3-7.
Listing 3-7. Explicitly Animating the Layer Bounds Property
CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"]; [boundsAnimation setFromValue:[NSValue valueWithRect:oldRect]]; [boundsAnimation setToValue:[NSValue valueWithRect:newRect]]; [boundsAnimation setDuration:5.0f]; [layer setBounds:NSRectToCGRect(newRect)]; [layer addAnimation:boundsAnimation forKey:@"bounds"];
In Listing 3-6, we used the CABasicAnimation class, the primary animation object for basic animation. We take a deeper look at it shortly, but first we are going to set up a simple Xcode project to demonstrate basic layer animation.