Core Animation: Paths

April 10th, 2008
New iPad puzzle game:
Art Scrambles

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.

animate_rotate_element.png animate_along_path.png animate_rotate_scene.png
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.

1. Create an animation to spin alayer on its z-axis.
- (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.

2. Spin a layer on its z-axis.
- (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.

1. Create a 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);
}
2. Create an animation to apply the path over time.
- (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;
}
3. Move a layer along a path.
- (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.

1. Create an animation to rotate along all three axes
- (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.

2. Rotate a layer in all three dimensions.
- (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

Do all three animations 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