Lately I've been reading up on Core Animation (CA), and learning how to use a wider range of it's features.
Our company develops for both Mac and iOS, so where practical, I like to use techniques that are the same across platforms.
The Mac OS flavor of CA doesn't support view animation, or the new block-based animations that are available in iOS. On the other hand, Mac OS objects like views and windows have an animator proxy object, and you can simply send messages to change properties to the proxy instead of sending them to the view, and the proxy creates an animation for you.
Both platforms support CAAnimation objects including CABasicAnimation and CAAnimationGroup.
On iOS, I've written cartoon style animations by using a mixture of view animation, CABasicAnimation, and CAKeyframeAnimation objects. That involves setting up completion callbacks that increment a counter and trigger the next step in the animation. There's a link to a tutorial in my sig that uses that approach, taken our of my company's "Kevin and Kell" cartoon reader.
I wanted to see if you could use animation groups to link a sequence of animations. It turns out you can, although there are some gotchas.
I created a project first in Mac OS, and then ported it over to iOS. The core of the code is the same between the two projects. the differences are that Mac OS uses NSView, and iOS uses UIViews, CGRects, CGPoints, etc. That was about all I needed to change to move the code from Mac to iOS.
The animation itself is fairly useless. It just animates our company logo, fading in, moving diagonally across the screen, rotating, falling (with an slide whistle sound), rotating back to it's original orientation, sliding sideways back to it's starting position, and then fading out again.
You can download the project at this link:
Creating a linked set of animations using a CAAnimationGroup
The core of the program is a routine called -doAnimation that triggers the animation sequence.
It creates a whole bunch of CABasicAnimation objects, each with a start time that is after the end of the previous animation. It then creates a CAAnimationGroup object and installs all the individual animations into that group. Its sets the duration of the animation group to the total time for all the component animations.
I created a variable that keeps track of the total time for all the animations so far, so it can figure out when to start the next animation in the sequence.
The animations are installed on the layer that backs the image view I am animating.
In order for each animation to build on the previous animation, you have to set fillMode = kCAFillModeForwards and removedOnCompletion = FALSE (which leaves the animation's effects in force once it's finished.) You have to do those steps for every animation in the group AND for the group animation. I tried setting removedOnCompletion to TRUE for one of the animations in the group, and it behaves very strangely.
I wanted to add a sound to the mix at a specific point in the animation sequence. Normally, with CABasicAnimation, you can set an object up as a delegate of the animation, and if it finds a animationDidStop:finished: method in the delegate, it will call it once that animation is complete. Apparently that doesn't work when an animation is part of a group. I think the reason is that the animationDidStop method is called "when the animation completes its active duration or is removed from the object it is attached to." In this case, the animation is part of a group, so it doesn't get removed from the group it belongs to.
The docs say that animations don't actually change the underlying properties that they animate (location, frame, opacity, transform, etc.) but only create the appearance
of that property being changed. They instruct you to change the underlying value through code after submitting the animation if you want the layer to keep the changes applied by the animation. However, there's a gotcha. CAAnimations are added to layers, not view objects. By default, layers do an implicit animation when you change one of their values. Simply executing the statement
layer.opacity = 0.0;
Causes the layer to fade away. You have to use a block of code like this if you want a change to a layer setting not
[CATransaction setValue: [NSNumber numberWithBool: YES]
imageOne.layer.opacity = 0.0;
Anyway, I will post all the code to the -doAnimation method in a reply to this thread (the forum won't let me post it here because the post would be too long. Sigh...)
One of the Core Animation books I'm reading says that you can actually use a CAAnimation object to animate properties in other objects than just the target for the animation. The only requirement is that you be able to build a key KVO expression that links to the target object (e.g. "parentView.sprite1View.legView.layer.position").
I had visions of targeting a group animation at a parent view, and then reaching into the subviews of the parent to animate crossfades, animate multiple views independently, etc. However, you can't add "run this animation now" animations to views. Only layers support that type of animation, and layers don't lend themselves to linking with KVO expressions like views do. In order to do the parentView.sprite1View.legView.layer.position" example I gave above, I would have to make most/all the views in the view hierarchy custom subclasses of UIImageView (or NSImageView on the Mac) and include properties that link to the other views. As far as I know, you can't create subclasses of layers, since layers are created by the system automatically for the views they are linked to.