Core Animation: Paths
We’ve seen early uses of animation in Mac OS X and some of the ways Core Animation is used in Leopard, but whether Core Animation will be broadly adopted depends in part on how easy it is to code. If it’s hard to code, developers will avoid it, but if it’s easy they’ll use it everywhere.
So let’s take a closer look at Core Animation from the developer’s perspective by writing some code, starting with three simple animations: spinning a wheel; moving that spinning wheel along an arbitrary path; and rotating that path in all three dimensions while the wheel spins and moves along it.
![]() |
![]() |
![]() |
| Spinning a wheel | Animating along a path | Rotating path in 3 dimensions |
Spinning a wheel
To spin a wheel, you need to create an animation to describe the spinning, then assign that animation to the layer you’d like to spin. “Spinning” means a transformation on the axis of movement, which in this case means the Z-axis because we’d like the wheel to spin parallel to the screen.
- (CAAnimation*)animationForSpinning {
// Create a transform to rotate in the z-axis
float radians = DegreesToRadians( 360 );
CATransform3D transform;
transform = CATransform3DMakeRotation(radians, 0, 0, 1.0);
// Create a basic animation to animate the layer's transform
CABasicAnimation* animation;
animation = [CABasicAnimation animationWithKeyPath:@"transform"];
// Now assign the transform as the animation's value. While
// animating, CABasicAnimation will vary the transform
// attribute of its target, which for this transform will spin
// the target like a wheel on its z-axis.
animation.toValue = [NSValue valueWithCATransform3D:transform];
animation.duration = 2; // two seconds
animation.cumulative = YES;
animation.repeatCount = 10000; "forever"
return animation;
}
Now that we have the animation, let’s assign it to a layer to spin it.
- (void)spinLayer:(CALayer*)layer {
// Create a new spinning animation
CAAnimation* spinningAnimation = [self animationForSpinning];
// Assign this animation to the provided layer's opacity attribute.
// Any subsequent change to the layer's opacity will
// trigger the animation.
[layer addAnimation:spinningAnimation forKey:@"opacity"];
// So let's trigger it now
layer.opacity = 0.99;
}
Moving the spinning wheel along an arbitrary path
To move a layer along a path, you need to create a path, create an animation to apply that path over time, then assign that animation to the position of the layer you’d like to move along the path.
- (void)createPath:(int)size inRect:(NSRect)rect {
// Calculate the path's bounds, centered in the given rect.
float left = rect.size.width/2 - size/2;
float top = rect.size.height/2 - size/2;
CGRect pathRect = CGRectMake(left, top, size, size);
// Create a simple rectangular path.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, nil, pathRect);
CGPathCloseSubpath(path);
[self setPath:path];
CGPathRelease(path);
}
- (CAAnimation*)animationForPath:(CFPathRef)thePath {
// Create a keyframe animation, since the positions along the
// path will need to be interpolated from the path's points.
CAKeyframeAnimation* animation = [CAKeyframeAnimation animation];
animation.path = thePath;
animation.duration = 2; // two seconds
animation.repeatCount = 10000; // "forever"
return animation;
}
- (void)animateLayer:(CALayer*)layer OnPath:(CFPathRef)thePath {
// Create the animation for the path, then assign it to the
// provided layer's position attribute. Any subsequent change
// to the layer's position will trigger the animation.
CAAnimation* pathAnimation = [self animationForPath:thePath];
[layer addAnimation:pathAnimation forKey:@"position"];
// So let's trigger it now
layer.position = CGPointZero;
}
Rotating the path in all three dimensions
To rotate the path in three dimensions is just a broader application of spinning the wheel. You need to create an animation to describe the rotation, then assign that animation to the layer you’d like to rotate.
- (CAAnimation*)animationForRotationX:(float)x Y:(float)y andZ:(float)z {
// Create a transform to rotate in all three axes
float radians = DegreesToRadians( 360 );
CATransform3D transform;
transform = CATransform3DMakeRotation(radians, x, y, z);
// Create a basic animation to animate the layer's transform
CABasicAnimation* animation;
animation = [CABasicAnimation animationWithKeyPath:@"transform"];
// Now assign the transform as the animation's value. While
// animating, CABasicAnimation will vary the transform
// attribute of its target for all three axes.
animation.toValue = [NSValue valueWithCATransform3D:transform];
animation.duration = 2; // two seconds
animation.cumulative = YES;
animation.repeatCount = 10000; "forever"
return animation;
}
Now that we have the animation, let’s assign it to a layer to rotate it.
- (void)rotateLayer:(CALayer*)layer {
// Create a new rotation animation
CAAnimation* rotatingAnimation;
rotatingAnimation = [self animationForRotationX:0.5 Y:0.5 Z:0.5];
// Assign this animation to the provided layer's opacity attribute.
// Any subsequent change to the layer's opacity will
// trigger the animation.
[layer rotatingAnimation forKey:@"opacity"];
// So let's trigger it now
layer.opacity = 0.99;
}
Putting it all together
- (void)doAllThreeAnimationsTogether {
CALayer* rootLayer = [self rootLayer];
CALayer* rotationLayer = [[CALayer layer] retain];
rotationLayer.bounds = rootLayer.bounds;
rotationLayer.frame = rootLayer.frame;
[rootLayer addSublayer: rotationLayer];
CGImageRef imageRef = [self getImage]; // any image will do
// Create the spinning layer
CALayer *spinner = [CALayer layer];
CGRect frame = rotationLayer.frame;
frame.origin = origin;
frame.size.width = CGImageGetWidth(imageRef);
frame.size.height = CGImageGetHeight(imageRef);
spinner.frame = frame;
frame.origin = CGPointZero;
spinner.bounds = frame;
spinner.contents = (id)imageRef; // assign the image to spinner
// And spin it
[rotationLayer addSublayer: spinner];
// Create the path, then animate the spinning layer along it.
CFPathRef path = [self createPath:[self pathSize] inRect:[self bounds]];
[self animateLayer:spinner onPath:path];
// Finally, rotate the parent layer.
[self rotateLayer: rotationLayer];
}
Conclusion
Getting these three animations working was much easier that expected. What was not expected was how fun Core Animation is, so much so that there was time and enthusiasm left over for a few extra frills like multiple paths (circle, square, star, and an squiggly Bezier path); multiple spinning wheels; varying how fast the layer spins and moves along the path, and how quickly the whole thing rotates; and varying the size of the path.
Core Animation models animation elegantly yet flexibly, and automates much of the work for you. Freed from drudgery, the developer can remain focused on ideas rather than details. In that sense, Core Animation echoes one of the chief benefits of Apple design: it nurtures creativity by getting out of your way.
You know a technology is well-designed when using it feels like playing, when the obstacles fall away, leaving you free to explore. A technology like that reawakens the powerful feeling that drew you to computers and to coding in the first place.
There’s little doubt, based on this experience, that developers will let their own imaginations run free with Core Animation.
Download and try it
Download (requires Leopard): application | source code



hey, nice tutorial! I’m in a cocoa class at a junior college, and I’ve done the big nerd ranch cocoa class… but I’m still struggling. I primarily come from web and database programming, but I’m also somewhat proficient in processing (processing.org)… so while my attempt to add an “angle” slider to the star path failed this morning, I felt like I almost got it. I’ll try it again later. Thanks very much for posting this code, it really helps me understand what’s going on when I can get visual feedback for the abstract concepts. That’s what I like about processing, and so maybe using Core Animation as an area to really dig into will help me cement some of the cocoa concepts…
-steve