Core Animation, you’ve been hearing about how great it is, how it makes animation easier and how cool it is. Well you’re about to find out first hand that all of the things you’ve heard are TRUE. We’ll cover a little background first and then jump into a simple Cocoa example on how to build Core Animation functionality into your application using a couple of completely contrived examples (For those that want something a bit more advanced, I’ve posted some work-in-progress code, that shows how to use layering to create a cheap Cover Flow knock off. Hey it’s a work in progress. See the end of the tutorial for links).
Table of Contents
Core Animation… What it is and how it works (in a nutshell)
Core Animation is a technology that allows developers to produce smooth animated user interfaces. Some would argue it’s purely eye-candy, but visual feedback is an important element of good UI design. Core Animation unloads a lot of the complexity in generating animated UI elements by providing an additional layer of abstraction between the programmer and underlying software/hardware interfaces.
If you are on Leopard (or have an iPhone or iPod Touch), you’ve seen examples of Core Animation already. Cover Flow is one example. For another more subtle example do the following:
1) Open any image file in Preview.app
2) Press the Zoom In button and watch the zoom process as it occurs
What you’ll notice is that the image smoothly transitions from the original size to the new size. In Tiger you’d get one of two things.
1) If the engineer took the time to keyframe the transitions from one step to another you’d also get the smooth transition. He or She probably didn’t because it was a lot of work to do this manually.
2) You’d notice that in going from one size to the other that it jumped instantly from the original size to the new one.
The difference between Tigers Preview zoom and Leopards is subtle, but very graceful. It’s very Apple, if you will. And as you’ll see, basic Core Animation transitions are dead simple to deploy (of course you can get extremely complicated for truly stunning effects, but that is not for this tutorial).
Ok. So how does Core Animation “work” ? That isn’t so simple. But think of Core Animation as an extension of AppKit which is the applications framework that drives much of what you see on Mac OS X.
Core Animation sits between the programmer accessible elements of basic Cocoa animations and the underlying graphics hardware powered by OpenGL. It serves as part of the graphics unification layer which previously included QuickTime, Core Image and Quartz. Core Animation introduces a new concept to AppKit based views called “Layers.”
Traditionally animation in Mac OS X is done via AppKit’s Animation API’s. With Core Animation views are backed by an animation layer, which is effectively a cached copy of the view (remember, buttons, sliders, image wells, windows etc… are all considered “views”). The cached layer is what is processed by the video hardware to produce the smooth visual effects we see in Core Animation enabled applications. If a view has a layer it is said to be “layer-backed.”
Layer-backed views work in a hierarchy. Take for example an application Window with a button and table view. If that window is layer-backed, then by default the button and table view are also layer-backed. Each subview of a layer-backed view is layer-backed (by design). The converse isn’t true. If we create a backing layer for our button, the application window isn’t layer-backed. This is important to remember since if we forget and try to apply Core Animation to a non-backed view, nothing will happen.
At this point it might be tempting to conclude that you should always back the top most view in a rendering tree with a layer. If you do so, then all of your subviews get layers automatically. Right? However, it’s important to remember that a layer is nothing more than a cached copy of a particular element. For each layer-backed view you have, there exists some allocation of memory (video memory) that is being occupied by that cached copy. So keep this in mind when setting up your layers. Back the elements you need, and leave the rest off (when convenient to do so) and conserve that video RAM for something else.
Two other things I should note is that Core Animation uses OpenGL for processing the rendering queue. But since almost all new Mac’s ship with multiple CPU’s/cores, Core Animation is threaded to take advantage of that as well. So while an animation is running your application can continue to do other things.
Animation
When we talk about animation we’re talking about changing a property over time. As you’ll see in the example below, we can vary a number of properties of views including height, width, rotation, opacity and color to name a few. In order to get smooth animation we have calculate the intermediate values between the initial and final states and draw them accordingly. This can be complicated and messy.
Core Animation takes a lot of the work out of the equation for simple animation types. Core Animation introduces a new type of proxy in communicating information about visual changes in state. This object is called the “animator” and each subclass of NSObject has one. When we want to performa a Core Animation transition from one state to another, rather than tell the object of interest what its new properties are, we tell the animator and the animator will then do all of the work for us. Again, that work includes determining all of the relevant state changes of the object to get from state A to state B.
For example, let’s say we have a method called changeAlpha and it changes the opacity of a view element (an NSImageView). Traditionally we’d do it like so:
[imageWell setAlphaValue:0.5];
When the method is called, the alpha value will change, and it will do so in one step going from fully opaque to half transparent the next time the view is redrawn. Now with Core Animation we’ll talk to the animator:
[[imageWell animator] setAlphaValue:0.5];
We first retrieve the imageWell’s animator and tell it to set the alpha value. It will then interpolate between the current value (1.0) and the new value (0.5) in a series of steps over the default duration of time (0.25 seconds, unless changed).
Let’s Animate…
You can download the completed project we’ll discus. Go ahead… I’ll wait.
Open up the Xcode project and press Build and Go. When the application launches you’ll see an NSImageView with a picture of our good friend Bubb Rubb. Click the Shift button (don’t mess with the Rotate just yet). You’ll see the image shift from left to right click it again to send it back. Now, select “Use Core Animation” and click Shift again (again, don’t mess with Rotate). You’ll see the image view move from left to right. But smoothly this time. The difference between the two modes is one line of code (seriously).
Ok. Go back to Xcode and click on the file CAController.m. That is all of the code that creates and drives the project. Look at the IBAction method “shift:”.
Here is the listing:
- (IBAction)shift:(id)sender
{
int mult = [sender intValue] ? 1.0 : -1.0;
//Set the frame position origin on x +/- 200
//depending on the current state
position.origin.x += (mult * 200);
//Now set the frame origin
if([useCoreAnimation state] == NSOffState)
[view setFrame:position];
else
[[view animator] setFrame:position];
}
The first two lines in the code create a multiplier based on the Shift button state and set the frame origin of the NSImageView. This is basic code. The important stuff is below that. Normally if we want to move the frame of our NSImageView (called view) we’d do something like this:
[view setFrame:position];
Simple. In doing so, the frame would be repositioned and redrawn. But it would happen all at once. Going from point A -> point B in one hop. Now if the “Use Core Animation” switch is checked, we do this instead:
[[view animator] setFrame:position];
Rather than talking directly to the view, we talk to the view’s animator. Think of the view as a puppet and the animator the puppeteer. The animator controls how the view is transitioned from point A -> point B. You can control aspects of the animator by modifying the NSAnimationContext. For example if we wanted a slow transition we could do something like:
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:3.0f];
[[view animator] setFrame:position];
[NSAnimationContext endGrouping];
Try adding that to the else block and see what happens. The NSImageView should transition more slowly.
Ok. Now let’s look at Rotate, you know the one I didn’t want you to mess with. It’s pretty much the same code one line of code except this time we are setting the frames rotation when Core Animation is enabled.
[[view animator] setFrameRotation:[rotate floatValue]];
Go back to the application and do the following:
1) Enable Core Animation and press Shift
2) Now turn the Rotate dial and let go.
Bubb smoothly rotates from one angle to another. Now… Let’s get to something important that I didn’t take into account in this code. If you rotate Bubb a little bit, press shift, rotate him again a bit, press shift… do this a few times and you’ll notice that the NSImageView size changes and distorts in odd ways. This is because in all of this code, we haven’t taken into account the effect we are having on the views transform. We’ll cover transforms at a later date, but for now, keep this in mind when performing complex operation types. If you view starts to distort you need to think about your transforms…
One thing I glossed over is how to enable Core Animation for our project. Remember that we need a layer backed view (and that all subviews of a layer backed view are automatically backed). So open the Resources disclosure triangle in the Xcode project, and double-click on MainMenu.nib. This will fire up Interface Builder.
In IB, press command-shift-I and double-click the Window icon (or if it’s already open select the actual Window, the one with the view and buttons). In the Animation tab you should see:
That little checkbox means that the main application window is going to be layer backed (and hence any subview of that Window, which includes all controls, fields, data views etc…) will be layer backed as well, by default (even if they aren’t checked).
You can also do this programatically, and hence on-the-fly by calling the -setWantsLayer method on an object:
[view setWantsLayer:YES];
Again, it’s really that simple. In this case we’ve told the NSImageView that it and its subviews should have layers. In IB, we set the main application window (and hence its subviews, including the NSImageView) to have layers.
That’s it for our Intro to Core Animation. Like I mentioned above a more complicated example/work in progress can be found in the FileBrowser example which can be downloaded here. This one programmatically shows how to set up layers and organize them to create a Cover Flow like effect with images. There is also a thread that describes how to access the private API that Apple uses for Cover Flow here. Beware, Apple can change private API’s at any time, so don’t use them in a shipping application.
Next time we’ll talk about Core Animation classes and talk about how to use them to create more advanced animations and transitions. In the meantime additional information and examples of Core Animation can be downloaded from the Apple Developer Website (ADC account required).
Leave a Reply