Core Animation



Relevant resources


Documentation


Books


Web


Video


Sample code - Apple


Sample code - others


Sample code - class


Core Animation


Introduced in Leopard, Core Animation for the longest while seemed to be one of the most powerful frameworks that no one used.  However, more and more developers seem to be realizing the ways that subtle animations can improve the overall user experience for their applications.  The availability of more and more documentation on the subject hasn't hurt, either.


Core Animation is at the heart of the iPhone's interface, which makes sense, considering that it was originally developed for the iPhone and then brought across to the Mac (at a time when the iPhone had not yet been made public).  When people speak of how fluid and "human" the interface of the iPhone is, what they are really talking about are the animations everywhere in the interface.  Navigation controllers don't just instantly pop the next level onto the screen, they slide it in while sliding the old interface out.  The keyboard slides in from the bottom.  Scroll views bounce back when you hit their limits.  Alerts spring onto the screen.


All of these subtle effects are achieved using Core Animation.  Core Animation provides hardware-accelerated animation, while abstracting away all of the OpenGL calls and interpolation algorithms required to achieve this.  If you want to moved visual elements about in 2D, I highly recommend looking at Core Animation before taking the plunge with OpenGL ES.  


UIView animation


Let's start with the simplest way to add animated effects to your interface: using UIView animation blocks.  By default, changing the properties of a UIView causes it to instantly jump from one value to another.  To animate these property changes, you can enclose them in an animation block.  An animation block starts with code like


[UIView beginAnimations:nil context:NULL];


where the first parameter is a name for the animation, and context is anything you wish to have associated with the animation.  These are used with animation callbacks, which we'll cover in a bit.


When you've finished specifying everything that you want to animate, and all the settings for the animation, close out the block with 


[UIView commitAnimations];


This will cause all of the property changes done within the animation block to be processed and animated.  All of these values will be animated simultaneously as one group.  Note that Core Animation handles all animations on a background thread, so committing the animations will not cause your application to wait until they are done.  The animations will start in the background and processing will continue immediately to the statements following the commit message.


Not all properties on a view are animatable.  The ones that can be animated are indicated as such within the class documentation for UIView.


Several aspects of the animation performed within the block can be customized, but all settings must be made within the block.  Those changes will only apply to that particular animation block.


You can set the duration of an animation by placing the following within the animation block:


[UIView setAnimationDuration:1.0f];  // Set the entire animation to take 1 second from start to finish


How the animation proceeds for this duration can be customized by altering its timing curve.  The curve can determine if the object accelerates to a certain change velocity (eases in), decelerates at the end of the animation (eases out), or animates at a constant rate from the beginnning to the end (linear).  The curve is set using code like the following:


[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];


The possible values are UIViewAnimationCurveEaseInOut, UIViewAnimationCurveEaseIn, UIViewAnimationCurveEaseOut, and UIViewAnimationCurveLinear.


Animations can be disabled within a block (if you were wrapping one animation block in another and wanted to make some part instantaneous) by using 


[UIView setAnimationsEnabled:NO];


Animations can be repeated to cause shaking or pulsing effects.  You can set the number of times the animation repeats using the following:


[UIView setAnimationRepeatCount:1e100f];


(1e100f basically causes it to repeat forever).


By default, the animation will keep looping from the start state to the end one every time it repeats.  To make it animate from start to end, then back to the start, you can tell it to autoreverse:


[UIView setAnimationRepeatAutoreverses:YES];


If you have started one animation and want to change direction, yet have the new animation start smoothly from the current point of the old one, you can use


[UIView setAnimationBeginsFromCurrentState:YES];


As mentioned before, animations are performed on a background thread.  As such, the animations will not be run serially with your code.  In order to respond to the start and stop of an animation, you need to use delegate callback methods.  To do this, you first set the delegate for the current animation block:


[UIView setAnimationDelegate:self];


and then set a selector to use for a callback method that is either triggered on the start of the animation, end of the animation, or both:

[UIView setAnimationWillStartSelector:@selector(myAnimationHasStarted:context:)];

[UIView setAnimationDidStopSelector:@selector(myAnimationHasFinished:finished:context:)];

The methods either of these call need to have a specific format, like the following:


- (void)myAnimationHasStarted:(NSString *)animationID context:(void *)context

{

// Respond to the start of the animation

}


- (void)menuSlideUpAnimationHasFinished:(NSString *)animationID finished:(BOOL)finished context:(void *)context;

{

// Respond to the end of the animation

}


By using these callbacks, you can chain animations or do cleanup on views that animate offscreen, among other things.


UIView transitions


A different type of UIView animation is a transition from one view to another.  This is most commonly seen in applications that flip from one view to another (like in the default Utility Application template in Xcode).  An example of this in action is the following:


[UIView beginAnimations:nil context:NULL];

[UIView setAnimationDuration:1];

[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:YES];


[secondViewController viewWillAppear:YES];

[firstViewController viewWillDisappear:YES];

[firstView removeFromSuperview];

[self.view addSubview:tableView];

[firstViewController viewDidDisappear:YES];

[secondViewController viewDidAppear:YES];


[UIView commitAnimations];


This block of code flips between two views that are subviews of one root view.  Each view has its own controller, which is notified about the flip action.  The flip occurs from the left to the right.  


Other possible transitions include UIViewAnimationTransitionFlipFromRight, UIViewAnimationTransitionCurlUp, and UIViewAnimationTransitionCurlDown.


Layers


While we've seen how UIViews can be animated around, there are a lot of neat effects that you simply can't achieve by working with views alone.  This is where Core Animation CALayers come in.  A CALayer is a a lightweight rectangular display element that has its contents stored as a texture on the GPU, and can be moved around in a hardware-accelerated manner.  Core Animation was originally called LayerKit, which makes sense, given how CALayers are the basis for everything it does.


Every UIView is layer-backed on the iPhone (accessible via the layer property), and all of the animations we've done to this point were really just wrappers around lower-level animations applied to the UIView's layer.  UIViews don't add a lot of weight to a CALayer.  I've found that I can animate 50 translucent views at 60 frames per second, or 100 views at 30 FPS, and using layers doesn't speed this up significantly. 


If there isn't a significant benefit, performance-wise, to using CALayers, why would you choose them over UIViews?  To do more complex animations, you will need to apply CAAnimations to a CALayer.  However, you can just as easily do this to a UIView's layer property.  My primary reason for doing work with CALayers is that they are almost completely cross-platform between iPhone and Mac.  In fact, the entire Core Plot framework is based on CALayers for just this reason.  We can use the same codebase, with only a few platform-specific extensions, on either platform.


In order to access properties on CALayers, or anything else related to Core Animation, you will need to include the QuartzCore headers:


#import <QuartzCore/QuartzCore.h>


and add the QuartzCore framework to your project.


We briefly looked at CALayers when covering how to customize UIViews.  CALayers have many of the same properties as UIViews, including frame, bounds, and backgroundColor.  CALayers have a layer hierarchy that is identical to that of UIViews, where you can manage ordering of CALayers using methods like addSublayer:, insertSublayer:atIndex:, and removeFromSuperlayer.


Content is provided to a CALayer in one of two ways: it is either drawn in a Quartz context, as we have seen for UIViews, or it is set as a CGImage using the contents property of the layer.  To render Quartz content in a CALayer, you either subclass CALayer and override the -drawInContext: method:


- (void)drawInContext:(CGContextRef)context

{

// Do your drawing here

}


or set a controller as a delegate for a CALayer and implement the -drawLayer:inContext: delegate method:


- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context

{

// Do your drawing here

}


CALayer is designed to be highly extensible, without the need for subclassing.  It is a key-value coding compliant container class, which means that it acts like an NSDictionary, where you can use -setValue:forKey: and -valueForKey: to set and retrieve arbitrary values in a CALayer.  You can also identify individual layers by using their name property, which is a simple NSString.


A unique geometrical property on CALayer is its anchorPoint.  When rotation, scaling, and other animations are applied to a CALayer, they are done about the layer's anchorPoint.  Similarly, the position of a CALayer is based on its anchorPoint.  The anchorPoint of a layer is a value in X and Y normalized to 1.0 in either direction.  By default, that value is (0.5, 0.5), indicating the center of the layer.  You can change this to (0,0) for the upper left corner of the layer (on the iPhone), or (1.0, 1.0) for the bottom right.  Note that changing the anchorPoint of the layer will cause it to move, because it will try to maintain the same position, only now based on a different anchorPoint.


It is easy when setting the position of a layer to end up with blurred layers because you forgot to take into account the center.  You can actually find out these non-pixel-aligned layers using the Core Animation instrument in Instruments, by checking the "Color Misaligned Layers" option.  This will highlight misaligned layers in red, letting you easily identify them.


It is possible to render a layer into a Core Graphics context using its -renderInContext: method.  For example, the following will create an image of a layer:


UIGraphicsBeginImageContext(layer.bounds.size);

[layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();


Rendering into a vector context is more involved, because by default layers are rendered into contexts as bitmaps.  If you use the normal -renderInContext: in a PDF context, you will get a bunch of overlaid bitmap rectangles for each of the layers.  For the Core Plot framework, we changed the rendering architecture of CALayer to allow it to draw vector elements into a PDF context, so I recommend investigating how we do that in the CPLayer class within the framework. 



Special layer types


In addition to the very flexible CALayer, there are a number of specialist CALayer types.  These include


All UIViews use a CALayer by default for their backing layer.  The class for this layer can be changed by overriding the +layerClass method in a UIView subclass.  For example, the following code causes a UIView to have a CAEAGLLayer as its base layer, something we'll use when we get to OpenGL ES rendering:


+ (Class) layerClass 

{

return [CAEAGLLayer class];

}


A CATiledLayer is useful for displaying very large content.  The GPU on the first few models was stated to have a maximum texture size of 1024 x 1024 (which actually is 2048 x 2048 in my testing), meaning that no view or layer larger than either of those dimensions can be displayed on the screen.  A CATiledLayer works around this limitation by providing tiles of content as they are needed on the screen.


CAShapeLayer can be used to display a Core Graphics path, and then animate that path in neat ways.  To animate the path between two states, you simply provide a CAAnimation that has as its end value a new CGPathRef with the same number of control points as the original path (if the control point count differs between the two, it will still animate, but the result may not be what you desire).


A CATransformLayer lets you do 3-D geometrical manipulation of its sublayers using a transform.  We'll look at 3-D transforms a little later.


A CAReplicatorLayer gives you the ability to do some fun effects by rapidly generating many copies of a single layer and animating them around.  One application for this is in the generation of particle systems.


A CAGradientLayer provides the ability to do hardware-accelerated drawing of linear gradients.


Layer property animations


By default, properties on a CALayer that are marked as being animatable will automatically animate from one state to another over a span of 0.25 seconds if you set them using the normal accessors.  The exception to this are the backing layers for UIViews, which have this automatic property animation disabled by default.


If you wish to override the animation for a property, you can either explicitly use a custom animation for dealing with it (described in a bit) or you can change the animation that is returned for a given property.  One way to do this is by setting the actions dictionary on a layer to return a custom value for a given property.  As an example of this, to disable the default crossfades on change of content or subviews for a layer, you could use code like the following:


NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"onOrderIn",

                                   [NSNull null], @"onOrderOut",

                                   [NSNull null], @"sublayers",

                                   [NSNull null], @"contents",

                                   nil];

layer.actions = newActions;

[newActions release];


Another way of handling this is to override -animationForKey: in a CALayer subclass to return your custom animation for a given property.


Custom animations


As I mentioned, one of the major reasons for dealing with CALayers or accessing the layer property of a UIView is to use custom animations that go beyond the ones you can create in a UIView animation block.  To do this, you will use subclasses of CAAnimation: CABasicAnimation, CAKeyframeAnimation, CATransition, and CAAnimationGroup.


Simple movement


The simplest of these is a CABasicAnimation, which simply animates a layer from one state to another.  To animate a layer using a CABasicAnimation, you must first create the animation, then set up its attributes, and finally assign it to the layer.  As an example, the following code sets up a animation that moves a layer from its current position to a specific endpoint:


CABasicAnimation *moveAnimation = [CABasicAnimation animationWithKeyPath:@"position"];

moveAnimation.duration = 1.0f;

moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];


moveAnimation.removedOnCompletion = NO;

moveAnimation.fillMode = kCAFillModeForwards;

CGPoint currentPosition = viewToAnimate.layer.position;

CGPoint newPosition = CGPointMake(currentPosition.x + 100.0f, currentPosition.y);

moveAnimation.toValue = [NSValue valueWithCGPoint:newPosition];

[viewToAnimate.layer addAnimation:moveAnimation forKey:@"animateMovement"];


There are a few parts to this.  First the animation is created (as an autoreleased instance) and associated with the property that we will be animating.  Then we set the duration of the animation to 1 second, and tell it to use the ease-in-ease-out timing function.  The next two lines, setting the removedOnCompletion property to NO and the fillMode to kCAFillModeForwards make it so that the layer will not jump back to its starting position at the completion of the animation, but remain where it is.


We then set the toValue of the animation, which tells it what value to animate to.  This value needs to be an object, so in this case we wrap a CGPoint in an NSValue instance.  Helper methods are present to make this simple.


Finally, we add the animation to a layer and give it an arbitrary name, which causes the animation to be performed instantly for the layer.


One thing to note about this is that the position property for the layer will not reflect the final state of the animation.  To make it so it will, we can alter the above animation to replace the last line with


[viewToAnimate.layer addAnimation:moveAnimation forKey:@"position"];

viewToAnimate.layer.position = newPosition;


This should trigger the animation on a change of the position property, so when we set the position property of a layer to this new value it will use our custom animation.


Rotation around a circle


Core Animation adds some extensions to key-value coding, providing some helper keypaths to make certain tasks easier.  For example, you can try to rotate an object around a circle by creating a CATransform3D that should rotate the layer, then using that transform as a final state.  As long as the angle you're trying to rotate is less than Pi radians (180 degrees), this will animate fine.  However, if you try to use a transform greater than that, you will get surprising results, as the layer will animate in the opposite direction to that location on the circle.  This comes because Core Animation tries to find the optimal path between states, and in this case that translates to a shorter angle in the opposite direction.  


To make a full rotation around a circle, you would seem to have to break up your rotation into 180 degree chunks.  However, there is an easier way to do this using a helper keypath:


CABasicAnimation* rotationAnimation;

rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];

rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 * rotations];

rotationAnimation.duration = duration;

rotationAnimation.cumulative = YES;

rotationAnimation.repeatCount = 1.0; 

rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];

[myView.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];


Using the transform.rotation.z keypath, we are able to just specify an angle to go to when rotating the layer about the Z axis (rotation as we see it looking at the screen) and Core Animation does the rest of the math for us.


Animation along a path


For more complex animations, Core Animation provides CAKeyframeAnimation.  A CAKeyframeAnimation takes either a series of keyframes and times or a path to animate along.  Keyframes are specific positions or property values for the animation to hit as it progresses these keyframes have associated times, which are values normalized to 1.0 that indicate at what point along the the animation's timeline to hit the keyframe values.


CAKeyframeAnimations can also take in arbitrary Core Graphics paths to do complex animations along.  The following code demonstrates an animation along a Bezier curve:


CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];

pathAnimation.duration = 1.0f;

pathAnimation.calculationMode = kCAAnimationPaced;


CGPoint currentPosition = viewToAnimate.layer.position;

CGPoint endPoint = CGPointMake(currentPosition.x + 100.0f, currentPosition.y - 50.0f);

CGMutablePathRef curvedPath = CGPathCreateMutable();

CGPathMoveToPoint(curvedPath, NULL, currentPosition.x, currentPosition.y);

CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, currentPosition.y, endPoint.x, currentPosition.y, endPoint.x, endPoint.y);

pathAnimation.path = curvedPath;

CGPathRelease(curvedPath);

pathAnimation.fillMode = kCAFillModeForwards;

pathAnimation.removedOnCompletion = NO;

[viewToAnimate.layer addAnimation:pathAnimation forKey:@"animateMovementUsingPath"];


Pop-in animation


CAKeyframeAnimations can be used to animate more than the position of a layer.  The following uses a CAKeyframeAnimation to generate a "pop-in" effect on a layer:


CAKeyframeAnimation *boundsOvershootAnimation = [CAKeyframeAnimation animationWithKeyPath:@"bounds.size"];

CGSize startingSize = CGSizeZero;

CGSize overshootSize = CGSizeMake(targetSize.width * (1.0f + POPINOVERSHOOTPERCENTAGE), targetSize.height * (1.0f + POPINOVERSHOOTPERCENTAGE));

CGSize undershootSize = CGSizeMake(targetSize.width * (1.0f - POPINOVERSHOOTPERCENTAGE), targetSize.height * (1.0f - POPINOVERSHOOTPERCENTAGE));

NSArray *boundsValues = [NSArray arrayWithObjects:[NSValue valueWithCGSize:startingSize],

                         [NSValue valueWithCGSize:overshootSize],

                         [NSValue valueWithCGSize:undershootSize],

                         [NSValue valueWithCGSize:targetSize], nil];

[boundsOvershootAnimation setValues:boundsValues];


NSArray *times = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0f],

                  [NSNumber numberWithFloat:0.5f],

                  [NSNumber numberWithFloat:0.9f],

                  [NSNumber numberWithFloat:1.0f], nil];    

[boundsOvershootAnimation setKeyTimes:times];



NSArray *timingFunctions = [NSArray arrayWithObjects:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], 

                            [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],

                            [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],

                            nil];

[boundsOvershootAnimation setTimingFunctions:timingFunctions];

boundsOvershootAnimation.fillMode = kCAFillModeForwards;

boundsOvershootAnimation.removedOnCompletion = NO;


Grabbing values mid-animation


There are times when you would like to query the current state of an animation.  For example, you might want to interrupt an animation and set the current value of a layer to whatever the currently animated state is.  The properties on a layer or UIView will only express the start and end values that you set, they will not be updated during an animation.  To get the instantaneous state of a layer, you need to look at its presentationLayer.  The presentationLayer for a layer is created as a clone of it that is what is actually displayed on the screen during an animation.


As an example, the following code will grab the current frame of a moving UIView and set the UIView to match:


movingView.frame = [[movingView.layer presentationLayer] frame];

[movingView.layer removeAnimationForKey:@"movementAnimation"];


Grouping animations


If you have a series of animations that you would like to synchronize with one another, you can use a CAAnimationGroup.  A CAAnimationGroup is a subclass of CAAnimation, with all the same properties, that can have a set of animations assigned to it.  For example, the following block of code is used to animate an image along a curved path while shrinking it and fading it out (this is the code I use for saving equations in Pi Cubed):


UIImageView *imageViewForAnimation = [[UIImageView alloc] initWithImage:imageToAnimate];

imageViewForAnimation.alpha = 1.0f;

CGRect imageFrame = imageViewForAnimation.frame;

viewOrigin.y = viewOrigin.y + imageFrame.size.height / 2.0f;

viewOrigin.x = viewOrigin.x + imageFrame.size.width / 2.0f;


imageViewForAnimation.frame = imageFrame;

imageViewForAnimation.layer.position = viewOrigin;

[self.view addSubview:imageViewForAnimation];


// Set up fade out effect

CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];

[fadeOutAnimation setToValue:[NSNumber numberWithFloat:0.3]];

fadeOutAnimation.fillMode = kCAFillModeForwards;

fadeOutAnimation.removedOnCompletion = NO;


// Set up scaling

CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size"];

[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(40.0f, imageFrame.size.height * (40.0f / imageFrame.size.width))]];

resizeAnimation.fillMode = kCAFillModeForwards;

resizeAnimation.removedOnCompletion = NO;


// Set up path movement

CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];

pathAnimation.calculationMode = kCAAnimationPaced;

pathAnimation.fillMode = kCAFillModeForwards;

pathAnimation.removedOnCompletion = NO;


CGPoint endPoint = CGPointMake(480.0f - 30.0f, 40.0f);

CGMutablePathRef curvedPath = CGPathCreateMutable();

CGPathMoveToPoint(curvedPath, NULL, viewOrigin.x, viewOrigin.y);

CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, viewOrigin.y, endPoint.x, viewOrigin.y, endPoint.x, endPoint.y);

pathAnimation.path = curvedPath;

CGPathRelease(curvedPath);


CAAnimationGroup *group = [CAAnimationGroup animation]; 

group.fillMode = kCAFillModeForwards;

group.removedOnCompletion = NO;

[group setAnimations:[NSArray arrayWithObjects:fadeOutAnimation, pathAnimation, resizeAnimation, nil]];

group.duration = 0.7f;

group.delegate = self;

[group setValue:imageViewForAnimation forKey:@"imageViewBeingAnimated"];


[imageViewForAnimation.layer addAnimation:group forKey:@"savingAnimation"];


[imageViewForAnimation release];


Transactions


Another way to group animations is to use a Core Animation CATransaction.  CATransactions are very similar to UIView animation blocks in that you start them, set up some animations, then end them, and everything in that block has some attributes applied to it.  If you create multiple animations within a CATransaction, those animations will all start at the same time.


To start a transaction, you would use code like the following:


[CATransaction begin];


and to commit the transaction you'd use the following:


[CATransaction commit];


You can set properties on a CATransaction to override the attributes of all animations within it by using specific key values.  For example, to set the duration of all animations within the transaction to 1 second you could use the following:


[CATransaction setValue:[NSNumber numberWithFloat:1.0f] forKey:kCATransactionAnimationDuration];

One of the most useful things you can do with a CATransaction is to disable all animations within its scope, including default property animations.  This can be done using the following code:


[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];


Transforms


We were introduced to the concept of transforms with the 2-D CGAffineTransforms in Core Graphics.  Core Animation extends this concept by making the transforms three-dimensional.


Like with CGAffineTransforms, CATransform3Ds are C structs that contain a matrix for manipulating a layer.  They can be constructed using functions like CATransform3DMakeRotation(), CATransform3DMakeScale(), and CATransform3DMakeTranslation() and modified using CATransform3DScale(), CATransform3DRotate(), and CATransform3DTranslate().


However, there are a few new twists to these transforms.  Because they are in 3-D, rotations now need to have an axis specified about which the rotation takes place.  An axis of (0,0,1) would rotate the layer about the Z axis, which points out from the screen.  This would mimic a normal 2-D rotation.  If that axis was (1,0,0), the layer would rotate about the X axis toward the viewer.  This can lead to some really cool effects.


For example, you can rotate your layers in response to touch events using code like the following:


CGFloat totalRotation = sqrt(displacementInX * displacementInX + displacementInY * displacementInY);

CATransform3D rotationalTransform = CATransform3DRotate(currentTransform, totalRotation * M_PI / 180.0, ((displacementInX/totalRotation) * currentTransform.m12 + (displacementInY/totalRotation) * currentTransform.m11), 

((displacementInX/totalRotation) * currentTransform.m22 + (displacementInY/totalRotation) * currentTransform.m21), 

((displacementInX/totalRotation) * currentTransform.m32 + (displacementInY/totalRotation) * currentTransform.m31));


transformed.sublayerTransform = rotationalTransform;


which takes the displacement of your finger in X and Y on the screen and rotates the sublayers of a layer in 3-D to match your gesture.  I actually use code like this to rotate molecular structures in Molecules, something we'll discuss in the class on OpenGL ES.


Finally, you can tweak these 3-D transforms to generate a cool perspective effect, like you see in views as they flip over or in Apple's CoverFlow.  To do this, you need to manually set a small value to the m34 matrix element of the CATransform3D struct.  The following provides an example of how you would do this:


UIView *myView = [[self subviews] objectAtIndex:0];

CALayer *layer = myView.layer;

CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;

rotationAndPerspectiveTransform.m34 = 1.0 / -500;

rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0f * M_PI / 180.0f, 0.0f, 1.0f, 0.0f);

layer.transform = rotationAndPerspectiveTransform;


Touch interaction


By default, CALayers do not respond to touch events.  In most cases, if you are looking for interaction in your application, you are better off simply using UIViews and accessing their backing layers for custom animations or transforms.  However, there are occasions where you might want to have your Core Animation layers respond to touch events.


To do this, you will need to perform hit testing on your layers.  To find which layer would be hit by a touch at a given coordinate, you could use code like the following:


CALayer *hitLayer = [self.layer hitTest:pointOfTouch];


The -hitTest: method uses CALayer's -containsPoint: method to determine whether or not a touch would hit a given layer.  You can subclass CALayer and define your own implementation of this to ignore touches or to respond to them outside of the layer's normal boundaries.  For example, the following code will disable touch response for a given CALayer:


- (BOOL)containsPoint:(CGPoint)thePoint

{

return NO;

}