Chapter 4. Animation

Animation is the visible change of an attribute over time. The changing attribute might be positional: something moves or changes size. But other kinds of attribute can animate as well. For example, a view’s background color might change from red to green, not instantly, but perceptibly fading from one to the other. Or a view might change from opaque to transparent, not instantly, but perceptibly fading away.

Without help, most of us would find animation beyond our reach. There are just too many complications — complications of calculation, of timing, of screen refresh, of threading, and many more. Fortunately, help is provided. You don’t perform an animation yourself; you describe it, you order it, and it is performed for you. You get animation on demand.

Asking for an animation can be as simple as setting a property value; under some circumstances, a single line of code will result in animation:

myLayer.backgroundColor = [UIColor redColor].CGColor; // animate to red

And this is no coincidence. Apple wants to facilitate your use of animation. Animation is crucial to the character of the iOS interface. It isn’t just cool and fun; it clarifies that something is changing or responding. For example, one of my first apps was based on an OS X game in which the user clicks cards to select them. In the OS X version, a card was highlighted to show it was selected, and the computer would beep to indicate a click on an ineligible card. On iOS, these indications were insufficient: the highlighting felt weak, and you can’t use a sound warning in an environment where the user might have the volume turned off or be listening to music. So in the iOS version, animation is the indicator for card selection (a selected card waggles eagerly) and for tapping on an ineligible card (the whole interface shudders, as if to shrug off the tap).

(If you’re looking to create a complete constantly running animated world, as for certain types of game, look into Sprite Kit, which is new in iOS 7. This book doesn’t discuss Sprite Kit, but an understanding of the concepts in this chapter will prepare you very well for Sprite Kit.)

Note

The Simulator’s Debug → Toggle Slow Animations menu item helps you inspect animations by making them run more slowly.

Drawing, Animation, and Threading

When you change a visible view property, even without animation, that change does not visibly take place there and then. Rather, the system records that this is a change you would like to make, and marks the view as needing to be redrawn. You can change many visible view properties, but these changes merely constitute an accumulated set of instructions. Later, when all your code has run to completion and the system has, as it were, a free moment, then it redraws all views that need redrawing, applying their new visible property features. Let’s call this the redraw moment. (I’ll explain what the redraw moment really is later in this chapter.)

You can see that this is true simply by changing some visible aspect of a view and changing it back again, in the same code: on the screen, nothing happens. For example, suppose a view’s background color is green. Suppose your code changes it to red, and then later changes it back to green:

// view starts out green
view.backgroundColor = [UIColor redColor];
// ... time-consuming code goes here ...
view.backgroundColor = [UIColor greenColor];
// code ends, redraw moment arrives

The system accumulates all the desired changes until the redraw moment happens, and the redraw moment doesn’t happen until after your code has finished, so when the redraw moment does happen, the last accumulated change in the view’s color is to green — which is its color already. Thus, no matter how much time-consuming code lies between the change from green to red and the change from red to green, the user won’t see any color change at all.

That’s why you don’t order a view to be redrawn; rather, you tell it that it needs redrawing — setNeedsDisplay — at the next redraw moment. It’s also why I used delayed performance in the contentMode example in Chapter 2: by calling dispatch_after, I allowed the redraw moment a chance to happen, thus giving the view some content, before resizing the view. This use of delayed performance to let a redraw moment happen is quite common; later in this chapter I’ll suggest another way of accomplishing the same goal.

Similarly, when you ask for an animation to be performed, the animation doesn’t start happening on the screen until the next redraw moment. (You can force an animation to be performed immediately, but this is unusual.)

Now let’s talk about the mechanism by which animation is performed. It’s all a kind of ingenious illusion. Think of the animation as a kind of movie, a cartoon, interposed between the user and the “real” screen. While the animation lasts, this movie is superimposed onto the screen. When the animation is finished, the movie is removed, revealing the state of the “real” screen behind it. The user is unaware of all this, because (if you’ve done things correctly) at the time that it starts, the movie’s first frame looks just like the state of the “real” screen at that moment, and at the time that it ends, the movie’s last frame looks just like the state of the “real” screen at that moment.

So, when you reposition a view from position 1 to position 2 with animation, you can envision a typical sequence of events like this:

  1. The view is set to position 2, but there has been no redraw moment, so it is still portrayed at position 1.
  2. The rest of your code runs to completion.
  3. The redraw moment arrives. If there were no animation, the view would now suddenly be portrayed at position 2. But there is an animation, and it (the “animation movie”) starts with the view portrayed at position 1, so that is still what the user sees.
  4. The animation proceeds, portraying the view at intermediate positions between position 1 and position 2. The documentation describes the animation as now in-flight.
  5. The animation ends, portraying the view ending up at position 2.
  6. The “animation movie” is removed, revealing the view indeed at position 2.

Realizing that the “animation movie” is different from what happens to the real view is key to configuring an animation correctly. A frequent complaint of beginners is that a position animation is performed as expected, but then, at the end, the view “jumps” to some other position. This happens because you set up the animation but failed to move the view to match its final position in the “animation movie”; the “jump” happens because, when the “movie” is whipped away at the end of the animation, the real situation that’s revealed doesn’t match the last frame of the “movie”.

Animation takes place on an independent thread. You don’t have to worry about the details (thank heavens, because multithreading is generally rather tricky and complicated), but you can’t ignore it either. Your code runs independently of and possibly simultaneously with the animation — that’s what multithreading means — so communication between the animation and your code can require some planning.

Arranging for your code to be notified when an animation ends is a common need. Most of the animation APIs provide a way to set up such a notification. One use of an “animation ended” notification might be to chain animations together: one animation ends and then another begins, in sequence. Another use is to perform some sort of cleanup. A very frequent kind of cleanup has to do with handling of touches: while an animation is in-flight, if your code is not running, the interface by default is responsive to the user’s touches, which might cause all kinds of havoc as your views try to respond while the animation is still happening and the screen presentation doesn’t match reality. To take care of this, it’s common practice to turn off your app’s responsiveness to touches as you set up an animation and then turn it back on when you’re notified that the animation is over; locking down all the relevant situations so that this toggling of the app’s responsiveness is performed coherently can be challenging.

Since your code can run even after you’ve set up an animation, or might start running while an animation is in-flight, you need to be careful about setting up conflicting animations. Multiple animations can be set up (and performed) simultaneously, but trying to animate or change a property that’s already in the middle of being animated is an incoherency that can kill the animation there and then. You may sometimes do this intentionally as a way of interrupting an animation, but just as often you’ll want to take care not to let your animations step on each other’s feet.

Outside forces can interrupt your animations as well. The user might click the Home button to send your app to the background, or a phone call might come in while an animation is in-flight. The system deals coherently with this situation by simply canceling all in-flight animations when an app is backgrounded; you’ve already arranged before the animation for your views to assume the final states they will have after the animation, so no harm is done — when your app resumes, everything is in that final state you arranged beforehand. But if you wanted your app to appear to pick up an animation in the middle, where it left off, that would require some canny coding on your part.

UIImageView and UIImage Animation

UIImageView provides a form of animation so simple as to be scarcely deserving of the name; still, sometimes it might be all you need. You supply the UIImageView with an array of UIImages, as the value of its animationImages or highlightedAnimationImages property. This array represents the “frames” of a simple cartoon; when you send the startAnimating message, the images are displayed in turn, at a frame rate determined by the animationDuration property, repeating as many times as specified by the animationRepeatCount property (the default is 0, meaning to repeat forever, or until the stopAnimating message is received). Before and after the animation, the image view continues displaying its image (or highlightedImage).

For example, suppose we want an image of Mars to appear out of nowhere and flash three times on the screen. This might seem to require some sort of NSTimer-based solution, but it’s far simpler to use an animating UIImageView:

UIImage* mars = [UIImage imageNamed: @"Mars"];
UIGraphicsBeginImageContextWithOptions(mars.size, NO, 0);
UIImage* empty = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSArray* arr = @[mars, empty, mars, empty, mars];
UIImageView* iv = [[UIImageView alloc] initWithImage:empty];
CGRect r = iv.frame;
r.origin = CGPointMake(100,100);
iv.frame = r;
[self.view addSubview: iv];
iv.animationImages = arr;
iv.animationDuration = 2;
iv.animationRepeatCount = 1;
[iv startAnimating];

You can combine UIImageView animation with other kinds of animation. For example, you could flash the image of Mars while at the same time sliding the UIImageView rightward, using view animation as described in the next section.

In addition, UIImage supplies a parallel form of animation: an image can itself be an animated image. Just as with UIImageView, this really means that you’ve multiple images that form a sequence serving as the “frames” of a simple cartoon. You can designate an image as an animated image with one of these UIImage class methods:

animatedImageWithImages:duration:
As with UIImageView’s animationImages, you supply an array of UIImages. You also supply the duration for the whole animation.
animatedImageNamed:duration:
You supply the name of a single image file, as with imageNamed:, with no file extension. The runtime appends @"0" (or, if that fails, @"1") to the name you supply and makes that image file the first image in the animation sequence. Then it increments the appended number, gathering images and adding them to the sequence (until there are no more, or we reach @"1024").
animatedResizableImageNamed:capInsets:resizingMode:duration:
Combines an animated image with a resizable image (Chapter 2).

You do not tell an animated image to start animating, nor are you able to tell it how long you want the animation to repeat. Rather, an animated image is always animating, repeating its sequence once every duration seconds, so long as it appears in your interface; to control the animation, add the image to your interface or remove it from the interface, possibly exchanging it for a similar image that isn’t animated. Moreover, an animated image can appear in the interface anywhere a UIImage can appear as a property of some interface object.

In this example, I construct a sequence of red circles of different sizes, in code, and build an animated image which I then display in a UIButton:

NSMutableArray* arr = [NSMutableArray array];
float w = 18;
for (int i = 0; i < 6; i++) {
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(w,w), NO, 0);
    CGContextRef con = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(con, [UIColor redColor].CGColor);
    CGContextAddEllipseInRect(con, CGRectMake(0+i,0+i,w-i*2,w-i*2));
    CGContextFillPath(con);
    UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [arr addObject:im];
}
UIImage* im = [UIImage animatedImageWithImages:arr duration:0.5];
// assume self.b is a button in the interface
[self.b setImage:im forState:UIControlStateNormal];

View Animation

Animation is ultimately layer animation. However, for a limited range of properties, you can animate a UIView directly: these are its alpha, backgroundColor, bounds, center, frame, and transform. You can also animate a UIView’s change of contents. This list of animatable features, despite its brevity, will often prove quite sufficient. (If it doesn’t, you can drop down to a lower level and animate a layer, as described later in this chapter.)

Block-Based View Animation

The syntax for animating a UIView involves calling a UIView class method and expressing the desired animation in an Objective-C block. For example, suppose we have a UIView self.v in the interface, with a yellow background color, and we want to animate that view’s change of background color to red. This will do it:

[UIView animateWithDuration:0.4 animations:^{
    self.v.backgroundColor = [UIColor redColor];
}];

Note

Due to considerations of space, the older view animation methods beginAnimations:context: and commitAnimations, along with the other methods and constants to be used in conjunction with them, are not discussed in this edition of the book. In any case their use is “discouraged”, according to Apple.

Any animatable change made within an animations: block will be animated, so we can animate a change both to the view’s color and to its position simultaneously:

[UIView animateWithDuration:0.4 animations:^{
    self.v.backgroundColor = [UIColor redColor];
    CGPoint p = self.v.center;
    p.y -= 100;
    self.v.center = p;
}];

We can also animate changes to multiple views within the same animations: block. For example, suppose we want to make one view dissolve into another. We start with the second view present in the view hierarchy, but with an alpha of 0, so that it is invisible. Then we animate the change of the first view’s alpha to 0 and the second view’s alpha to 1.

In that case, we might like to place the second view in the view hierarchy just before the animation starts (invisibly, because its alpha starts at 0) and remove the first view just after the animation ends (invisibly, because its alpha ends at 0). An additional parameter, completion:, lets us specify what should happen after the animation ends:

UIView* v2 = // ... create and configure new view
v2.alpha = 0;
[self.v.superview addSubview:v2];
[UIView animateWithDuration:0.4 animations:^{
    self.v.alpha = 0;
    v2.alpha = 1;
} completion:^(BOOL finished) {
    [self.v removeFromSuperview];
}];

Code that isn’t about animatable view properties can appear in an animations: block with no problem, but we must be careful to keep any changes to animatable properties that we do not want animated out of the animations: block. In that example, in setting v2.alpha to 0, I just want to set it right now, instantly; I don’t want that change to be animated. So I’ve put that line before animations: block.

Sometimes, though, that’s not so easy; perhaps, within the animations: block, we must call a method that might perform animatable changes. New in iOS 7, the performWithoutAnimation: method solves the problem; it goes inside an animations: block, but whatever happens in its block is not animated. In this rather artificial example, the view jumps to its new position and then slowly turns red:

[UIView animateWithDuration:0.4 animations:^{
    self.v.backgroundColor = [UIColor redColor];
    [UIView performWithoutAnimation:^{
        CGPoint p = self.v.center;
        p.y -= 100;
        self.v.center = p;
    }];
}];

The material inside an animations: block (but not inside a performWithoutAnimation: block) orders the animation — that is, it gives instructions for what the animation will be when the redraw moment comes. If you change an animatable view property as part of the animation, you should not change that property again afterward; the results can be confusing. For example, try to guess what this code does:

[UIView animateWithDuration:2 animations:^{
    CGPoint p = self.v.center;
    p.y = 100;
    self.v.center = p;
    CGPoint p2 = self.v.center;
    p2.y = 300;
    self.v.center = p2;
}];

The result is not two successive animations. What we have here is just one animation containing conflicting orders: animate so that the view’s center y value becomes 100, and animate so that the view’s center y value becomes 300. The second order causes the animation described by the first order to be cancelled; the change described by the first animation is performed without animation. Therefore, when the animation runs, the view jumps so that its center y value is 100, and then animates down 200 points.

Even a change after the animation block can have confusing effects. Try to guess what this code does:

[UIView animateWithDuration:2 animations:^{
    CGPoint p = self.v.center;
    p.y = 100;
    self.v.center = p;
}];
CGPoint p2 = self.v.center;
p2.y = 300;
self.v.center = p2;

The view animates from where it already is to where its center y value becomes 300! The code inside the animations: block might as well never have happened. The code that follows the animations: block behaves as if it were inside the animations: block; it has become part of the animation — in fact, it has become the animation! It’s exactly the same as if that code had said this:

[UIView animateWithDuration:2 animations:^{
    CGPoint p2 = self.v.center;
    p2.y = 300;
    self.v.center = p2;
}];

These are edge cases, and you should avoid them. The moral is, after you’ve ordered an animatable view property to be animated inside an animations: block, don’t change that view property’s value again until after the animation is over.

View Animation Options

For the maximum in flexibility and power, call the UIView class method animateWithDuration:delay:options:animations:completion:. (The two methods I’ve already described are merely reduced versions of this method.) The parameters are:

duration
The duration of the animation: how long it takes (in seconds) to run from start to finish. You can also think of this as the animation’s speed. Obviously, if two views are told to move different distances in the same time, the one that must move further must move faster.
delay
The delay before the animation starts. The default is no delay.
options
A bitmask combining additional options (using the bitwise-or operator).
animations
The block containing view property changes to be animated.
completion
The block to run when the animation ends (or nil). It takes one BOOL parameter indicating whether the animation ran to completion. The block is called, with a parameter indicating YES, even if nothing in the animations: block triggers any animations. It’s fine for this block to order a further animation, thus chaining animations.

Here are some of the chief options: values you might wish to use:

Animation curve

An animation curve describes how the animation changes speed during its course. The term “ease” means that there is a gradual acceleration or deceleration between the animation’s central speed and the zero speed at its start or end. Specify one at most; UIViewAnimationOptionCurveEaseInOut is the default. Your choices are:

  • UIViewAnimationOptionCurveEaseInOut
  • UIViewAnimationOptionCurveEaseIn
  • UIViewAnimationOptionCurveEaseOut
  • UIViewAnimationOptionCurveLinear (constant speed throughout)
UIViewAnimationOptionRepeat
If included, the animation will repeat indefinitely. There is no way to specify a certain number of repetitions; you either repeat forever or not at all. This feels like an oversight (a serious oversight); I’ll suggest a workaround in a moment.
UIViewAnimationOptionAutoreverse
If included, the animation will run from start to finish (in the given duration time), and will then run from finish to start (also in the given duration time). The documentation’s claim that you can autoreverse only if you also repeat is incorrect; you can use either or both (or neither).

When using UIViewAnimationOptionAutoreverse, you will want to clean up at the end so that the view is back in its original position when the animation is over. To see what I mean, consider this code:

NSUInteger opts = UIViewAnimationOptionAutoreverse;
[UIView animateWithDuration:1 delay:0 options:opts animations:^{
    CGPoint p = self.v.center;
    p.x += 100;
    self.v.center = p;
} completion:nil];

The view animates 100 points to the right and then animates 100 points back to its original position — and then jumps 100 points to the right again. The reason is that the last actual value we assigned to the view’s center x is 100 points to the right, so when the animation is over and the “animation movie” is whipped away, the view is revealed still sitting 100 points to the right. The solution is to move the view back to its original position in the completion: handler:

CGPoint pOrig = self.v.center;
NSUInteger opts = UIViewAnimationOptionAutoreverse;
[UIView animateWithDuration:1 delay:0 options:opts animations:^{
    CGPoint p = self.v.center;
    p.x += 100;
    self.v.center = p;
} completion:^(BOOL finished) {
    self.v.center = pOrig;
}];

Working around the inability to specify a finite number of repetitions is not easy. Here’s one approach using recursion to chain animations:

- (void) animate: (int) count {
    CGPoint pOrig = self.v.center;
    NSUInteger opts = UIViewAnimationOptionAutoreverse;
    [UIView animateWithDuration:1 delay:0 options:opts animations:^{
        CGPoint p = self.v.center;
        p.x += 100;
        self.v.center = p;
    } completion:^(BOOL finished) {
        self.v.center = pOrig;
        if (count)
            [self animate:count-1];
    }];
}

If we call the animate: method with an argument of 2, our animation takes place three times and stops. There is always a danger, with recursion, of filling up the stack and running out of memory, but I think we’re safe if we start with a small count value.

There are also some options saying what should happen if another animation is already ordered or in-flight:

UIViewAnimationOptionBeginFromCurrentState
If this animation animates a property already being animated by an animation that is previously ordered or in-flight, then instead of canceling the previous animation (completing the requested change instantly), this animation will use the presentation layer to decide where to start, and, if possible, will “blend” its animation with the previous animation.
UIViewAnimationOptionOverrideInheritedDuration
Prevents inheriting the duration from a surrounding or in-flight animation (the default is to inherit it).
UIViewAnimationOptionOverrideInheritedCurve
Prevents inheriting the animation curve from a surrounding or in-flight animation (the default is to inherit it).

To illustrate UIViewAnimationOptionBeginFromCurrentState, consider the following:

[UIView animateWithDuration:1 animations:^{
    CGPoint p = self.v.center;
    p.x += 100;
    self.v.center = p;
}];
NSUInteger opts = 0;
[UIView animateWithDuration:1 delay:0 options:opts animations:^{
    CGPoint p = self.v.center;
    p.x = 0;
    self.v.center = p;
} completion:nil];

The result is that the view jumps 100 points rightward, and then animates leftward. That’s because the second animation caused the first animation to be thrown away; the move 100 points rightward was performed instantly, instead of being animated. But if we set opts to UIViewAnimationOptionBeginFromCurrentState, the result is that the view animates leftward from its current position, with no jump.

Even more interesting is what happens when we change x to y in the second animation. If opts is 0, the view jumps to the right and then animates upward. If opts is UIViewAnimationOptionBeginFromCurrentState, then the two animations are combined: the view animates diagonally (right by 100 points, and up to where its center y is 0).

An option such as UIViewAnimationOptionOverrideInheritedDuration comes into play when animations: blocks are nested, like this:

[UIView animateWithDuration:2 animations:^{
    CGPoint p = self.v.center;
    p.x += 100;
    self.v.center = p;
    NSUInteger opts = 0;
    [UIView animateWithDuration:0.5 delay:0 options:opts animations:^{
        self.v.backgroundColor = [UIColor blackColor];
    } completion:nil];
}];

If opts is 0, the color animation takes the same time as the center animation; it inherits the duration value of the surrounding animations: block. But if opts is UIViewAnimationOptionOverrideInheritedDuration, each animation has its own duration.

Canceling an in-flight animation at the UIView level is a tricky problem (as opposed to doing it at the CALayer level, where it’s pretty easy). The technique I use is to order another animation that brings the animated view immediately to its final state. But the second animation must not assign the animated property exactly the same value that the first animation assigned it, or nothing will happen; we need to generate a conflict between the two animations.

In this example, presume that cancel is executed while the animation started in animate is in-flight. Our goal in cancel is to bring the animate animation to its completion immediately. So in cancel we order a very slightly different, conflicting animation and then use the completion: handler to assign the view property its true final value:

-(void) animate {
    CGPoint p = self.v.center;
    p.x += 100;
    self.pFinal = p;
    [UIView animateWithDuration:4 animations:^{
        self.v.center = p;
    }];
}
-(void) cancel {
    [UIView animateWithDuration:0 animations:^{
        CGPoint p = self.pFinal;
        p.x += 1;
        self.v.center = p;
    } completion:^(BOOL finished) {
        CGPoint p = self.pFinal;
        self.v.center = p;
    }];
}

We can use the same trick to stop a repeating animation. In this example, animate launches a repeating, autoreversing animation of our view’s center. To stop the animation, cancel sets the view back to its original position:

-(void) animate {
    CGPoint p = self.v.center;
    self.pOrig = p;
    p.x += 100;
    NSUInteger opts = UIViewAnimationOptionAutoreverse |
    UIViewAnimationOptionRepeat;
    [UIView animateWithDuration:1 delay:0 options:opts animations:^{
        self.v.center = p;
    } completion: nil];
}
-(void) cancel {
    [UIView animateWithDuration:0 animations:^{
        self.v.center = self.pOrig;
    }];
}

If you prefer cancel to behave a bit less abruptly, gliding quickly into position rather than jumping, give the animation a longer duration and specify UIViewAnimationOptionBeginFromCurrentState:

-(void) cancel {
    NSUInteger opts = UIViewAnimationOptionBeginFromCurrentState;
    [UIView animateWithDuration:0.1 delay:0 options:opts animations:^{
        self.v.center = self.pOrig;
    } completion:nil];
}

Springing View Animation

New in iOS 7, there’s a built-in animation curve that causes a positional animation to behave as if it were being snapped into place by a spring. To use it, call animateWithDuration:delay:usingSpring.... For example:

[UIView animateWithDuration:0.8 delay:0
    usingSpringWithDamping:0.7 initialSpringVelocity:0
                   options:0 animations:^{
    CGPoint p = self.v.center;
    p.y += 100;
    self.v.center = p;
} completion:nil];

The damping: and initialSpringVelocity: parameters modify the behavior of the animation curve. If the damping is less than 1, there’s a waggle as the animated view assumes its final position; this waggle becomes quite pronounced at values less than about 0.7, and at values like 0.3 there are several waggles before the view settles into place.

The initial spring velocity seems to govern the tendency of the view to overshoot its final position on its first approach. Depending on the duration and damping amount, it may need to be quite large to make an appreciable difference; try setting it to 20 in the preceding example. You can have a lot of fun with smaller damping values and larger spring velocity values.

The options: values are the same as in the previous section. You might like to experiment with different animation curves; I think UIViewAnimationOptionCurveEaseIn looks nice, starting slowly from a stationary position and using the imaginary spring alone to snap into the final position.

Keyframe View Animation

New in iOS 7, a view animation can be ordered as a set of keyframes. (Previously, this was possible only at the CALayer level, as I’ll describe later in this chapter.) This means that you specify stages in the animation and those stages are joined together for you. You call animateKeyframesWithDuration:...; it has an animations: block, and inside that block you call addKeyframe... multiple times to specify each stage. Each keyframe’s start time and duration is between 0 and 1, relative to the animation as a whole.

For example, here I’ll waggle a view back and forth horizontally while moving it down the screen vertically:

__block CGPoint p = self.v.center;
[UIView animateKeyframesWithDuration:4 delay:0 options:0 animations:^{
    [UIView addKeyframeWithRelativeStartTime:0 relativeDuration:.25
                                  animations:^{
        p.x += 100;
        p.y += 50;
        self.v.center = p;
    }];
    [UIView addKeyframeWithRelativeStartTime:.25 relativeDuration:.25
                                  animations:^{
        p.x -= 100;
        p.y += 50;
        self.v.center = p;
    }];
    [UIView addKeyframeWithRelativeStartTime:.5 relativeDuration:.25
                                  animations:^{
        p.x += 100;
        p.y += 50;
        self.v.center = p;
    }];
    [UIView addKeyframeWithRelativeStartTime:.75 relativeDuration:.25
                                  animations:^{
        p.x -= 100;
        p.y += 50;
        self.v.center = p;
    }];
}];

In that code, there are four keyframes, evenly spaced: each is .25 in duration (one-fourth of the whole animation) and each starts .25 later than the previous one. In each keyframe, the view’s center x value increases and decreases by 100, alternately, while its center y value keeps increasing by 50.

The path and timing depends upon options whose names begin with UIViewKeyframeAnimationOptionCalculationMode. The default, if the options: argument is 0, is Linear. In our example, this means that the path followed by the view is a sharp zig-zag, the view seeming to bounce off invisible walls at the right and left. But if the calculation mode is Cubic, our view describes a smooth S-curve, starting at the view’s initial position and ending at the last keyframe point, and passing through the three other keyframe points like the maxima and minima of a sine wave.

In that example, because my keyframes are perfectly even, I could achieve the same effects by using the calculation modes Paced (same effect as Linear) and CubicPaced (same effect as Cubic). These two Paced options simply ignore the relative start time and relative duration values of the keyframes; you might as well pass 0 for all of them. Instead, they divide up the times and durations evenly, exactly as my code has done.

Finally, Discrete calculation mode means that the changed animatable properties don’t animate: the animation jumps to each keyframe.

The outer animations: block can contain other changes to animatable view properties, as long as they don’t conflict with the keyframe animations:; these are animated over the total duration. For example:

[UIView animateKeyframesWithDuration:4 delay:0 options:0 animations:^{
    self.v.alpha = 0;
    // ... and the rest as before ...

The result is that as the view zigzags back and forth down the screen, it also gradually fades away.

It is also legal to supply an animation curve as part of the options: argument. (The documentation fails to make this clear.) For example:

NSUInteger opts =
    UIViewKeyframeAnimationOptionCalculationModeLinear |
    UIViewAnimationOptionCurveLinear;
[UIView animateKeyframesWithDuration:4 delay:0 options:opts animations:^{

That’s two different senses of “Linear”. The first means that the path described by the moving view is a sequence of straight lines. The second means that the moving view’s speed along that path is steady.

Transitions

A transition is an animation that emphasizes a view’s change of content. Transitions are ordered using one of two methods:

  • transitionWithView:duration:options:animations:completion:
  • transitionFromView:toView:duration:options:completion:

The transition animation types are expressed as part of the options: bitmask:

  • UIViewAnimationOptionTransitionFlipFromLeft
  • UIViewAnimationOptionTransitionFlipFromRight
  • UIViewAnimationOptionTransitionCurlUp
  • UIViewAnimationOptionTransitionCurlDown
  • UIViewAnimationOptionTransitionCrossDissolve
  • UIViewAnimationOptionTransitionFlipFromBottom
  • UIViewAnimationOptionTransitionFlipFromTop

Warning

Be careful not to confuse these with the older transition options UIViewAnimationTransitionFlipFromLeft and so forth.

In this example, a UIImageView containing an image of Mars flips over as its image changes to a smiley face; it looks as if the image view were two-sided, with Mars on one side and the smiley face on the other:

[UIView transitionWithView:self.iv duration:0.8
        options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
    self.iv.image = [UIImage imageNamed:@"Smiley"];
} completion:nil];

In that example, I’ve put the content change inside the animations: block. That’s conventional but misleading; the truth is that if all that’s changing is the content, nothing needs to go into the animations: block. The change of content can be anywhere, before or even after this entire line of code. It’s the flip that’s being animated. You might use the animations: block here to order additional animations, such a change in a view’s center.

You can do the same sort of thing with a custom view that does its own drawing. Imagine that I have a UIView subclass, MyView, that draws either a rectangle or an ellipse depending on the value of its BOOL reverse property:

- (void)drawRect:(CGRect)rect {
    CGRect f = CGRectInset(self.bounds, 10, 10);
    CGContextRef con = UIGraphicsGetCurrentContext();
    if (self.reverse)
        CGContextStrokeEllipseInRect(con, f);
    else
        CGContextStrokeRect(con, f);
}

This code flips a MyView instance while changing its drawing from a rectangle to an ellipse or vice versa:

self.v.reverse = !self.v.reverse;
[UIView transitionWithView:self.v duration:1
        options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
    [self.v setNeedsDisplay];
} completion:nil];

During a transition, by default, the view’s appearance changes directly to its final appearance; in effect, a snapshot of the view’s final appearance has been taken beforehand. If that isn’t what you want, use UIViewAnimationOptionAllowAnimatedContent in the options bitmask.

In this example, outer is the view to be animated using a transition, and inner is a subview of outer that currently occupies part of its width. In the course of the transition, we increase the width of inner to occupy the entire width of outer:

[UIView transitionWithView:self.outer duration:1
        options:opts animations:^{
    CGRect f = self.inner.frame;
    f.size.width = self.outer.frame.size.width;
    f.origin.x = 0;
    self.inner.frame = f;
} completion:nil];

If opts is UIViewAnimationOptionTransitionFlipFromLeft, we see outer flip over to the same appearance it had before, and then the change in inner happens in a jump. But if opts also includes UIViewAnimationOptionAllowAnimatedContent, then inner is seen expanding gradually as the flip animation is completing.

transitionFromView:toView:duration:options:​completion: names two views; the first is replaced by the second, while their superview undergoes the transition animation. There are two possible configurations, depending on the options you provide:

Remove one subview, add the other
If UIViewAnimationOptionShowHideTransitionViews is not one of the options, then the second subview is not in the view hierarchy when we start; the transition removes the first subview from its superview and adds the second subview to that same superview.
Hide one subview, show the other
If UIViewAnimationOptionShowHideTransitionViews is one of the options, then both subviews are in the view hierarchy when we start; the hidden of the first is NO, the hidden of the second is YES, and the transition reverses these values.

In this example, a label lab is already in the interface. The animation causes the superview of lab to flip over, while at the same time a different label, lab2, is substituted for it:

UILabel* lab2 = [[UILabel alloc] initWithFrame:self.lab.frame];
lab2.text = @"Howdy";
[lab2 sizeToFit];
[UIView transitionFromView:self.lab toView:lab2 duration:0.8
    options:UIViewAnimationOptionTransitionFlipFromLeft
    completion:nil];

It’s up to you to make sure beforehand that the second view (toView:) has the desired position, so that it will appear in the right place in its superview.

Implicit Layer Animation

If a layer is already present in the interface and is not a view’s underlying layer, animating it can be as simple as setting a property. A change in what the documentation calls an animatable property is automatically interpreted as a request to animate that change. In other words, animation of layer property changes is the default! Multiple property changes are considered part of the same animation. This mechanism is called implicit animation.

Note

You cannot use implicit animation on a UIView’s underlying layer. You can animate a UIView’s underlying layer directly, but you must use explicit layer animation (discussed later in this chapter).

For example, in Chapter 3 we constructed a compass out of layers. The compass itself is a CompassView that does no drawing of its own; its underlying layer is a CompassLayer that also does no drawing, serving only as a superlayer for the layers that constitute the drawing. None of the layers that constitute the actual drawing is the underlying layer of a view, so a property change to any of them, once they are established in the interface, is animated automatically.

So, presume that we have a reference to the arrow layer (arrow). If we rotate the arrow by changing its transform property, that rotation is animated:

// the next line is an implicit animation
arrow.transform = CATransform3DRotate(arrow.transform, M_PI/4.0, 0, 0, 1);

CALayer properties listed in the documentation as animatable in this way are anchorPoint and anchorPointZ, backgroundColor, borderColor, borderWidth, bounds, contents, contentsCenter, contentsRect, cornerRadius, doubleSided, hidden, masksToBounds, opacity, position and zPosition, rasterizationScale and shouldRasterize, shadowColor, shadowOffset, shadowOpacity, shadowRadius, and sublayerTransform and transform (but not affineTransform!). In addition, a CAShapeLayer’s path, fillColor, strokeColor, lineWidth, lineDashPhase, and miterLimit are animatable; so are a CATextLayer’s fontSize and foregroundColor, and a CAGradientLayer’s colors, locations, and endPoint. (See Chapter 3 for discussion of those properties.)

Basically, a property is animatable because there’s some sensible way to interpolate the intermediate values between one value and another. The nature of the animation attached to each property is therefore just what you would intuitively expect. When you change a layer’s hidden property, it fades out of view (or into view). When you change a layer’s contents, the old contents are dissolved into the new contents. And so forth.

Warning

A layer’s frame is not an animatable property! To animate the changing of a layer’s frame, you’ll change other properties such as its bounds and position. Trying to animate a layer’s frame is a common beginner error.

Implicit layer animation doesn’t affect a layer as it is being created, configured, and added to the interface. Implicit animation comes into play when you change an animatable property of a layer that is already present in the interface.

Animation Transactions

Implicit animation operates with respect to a transaction (a CATransaction), which groups animation requests into a single animation. Every animation request takes place in the context of a transaction. You can make this explicit by wrapping your animation requests in calls to the CATransaction class methods begin and commit; the result is a transaction block. Additionally, there is an implicit transaction surrounding all your code, and you can operate on this implicit transaction without any begin and commit.

To modify the characteristics of an implicit animation, you modify the transaction that surrounds it. Typically, you’ll use these CATransaction class methods:

setAnimationDuration:
The duration of the animation.
setAnimationTimingFunction:
A CAMediaTimingFunction; timing functions are discussed in the next section.
setCompletionBlock:
A block to be called when the animation ends. The block takes no parameters. The block is called even if no animation is triggered during this transaction.

By nesting transaction blocks, you can apply different animation characteristics to different elements of an animation. But you can also use transaction commands outside of any transaction block to modify the implicit transaction. So, in our previous example, we could slow down the animation of the arrow like this:

[CATransaction setAnimationDuration:0.8];
arrow.transform = CATransform3DRotate(arrow.transform, M_PI/4.0, 0, 0, 1);

Another useful feature of animation transactions is to turn implicit animation off. It’s important to be able to do this, because implicit animation is the default, and can be unwanted (and a performance drag). To do so, call the CATransaction class method setDisableActions: with argument YES. There are other ways to turn off implicit animation (discussed later in this chapter), but this is the simplest.

setCompletionBlock: is an extraordinarily useful and probably underutilized tool. The transaction’s completion block signals the end, not only of the implicit layer property animations you yourself have ordered as part of this transaction, but of all animations ordered during this transaction, including Cocoa’s own animations. For example, consider what happens when you explicitly dismiss a popover with animation:

[myPopoverController dismissPopoverAnimated: YES];

There’s no completion block, and this isn’t your animation, so how can you learn when the animation is over and the popover is well and truly gone? A transaction completion block solves the problem.

CATransaction implements KVC to allow you to set and retrieve a value for an arbitrary key, similar to CALayer. An example appears later in this chapter.

Warning

An explicit transaction block that orders an animation to a layer, if the block is not preceded by any other changes to the layer, can cause animation to begin immediately when the CATransaction class method commit is called, without waiting for the redraw moment, while your code continues running. In my experience, this can cause trouble (animation delegate messages cannot arrive, and the presentation layer can’t be queried properly) and should be avoided.

Media Timing Functions

The CATransaction class method setAnimationTimingFunction: takes as its parameter a media timing function (CAMediaTimingFunction). This class is the general expression of the animation curves we have already met (ease-in-out, ease-in, ease-out, and linear), and you can use it with those very same predefined curves, by calling the CAMediaTimingFunction class method functionWithName: with one of these parameters:

  • kCAMediaTimingFunctionLinear
  • kCAMediaTimingFunctionEaseIn
  • kCAMediaTimingFunctionEaseOut
  • kCAMediaTimingFunctionEaseInEaseOut
  • kCAMediaTimingFunctionDefault

A media timing function is a Bézier curve defined by two points. The curve graphs the fraction of the animation’s time that has elapsed (the x-axis) against the fraction of the animation’s change that has occurred (the y-axis); its endpoints are therefore at {0,0} and {1,1}, because at the beginning of the animation there has been no elapsed time and no change, and at the end of the animation all the time has elapsed and all the change has occurred.

The curve’s defining points are its endpoints, and each endpoint needs only one Bézier control point to define the tangent to the curve. And because the curve’s endpoints are known, defining the two control points is sufficient to describe the entire curve. And because a point is a pair of floating-point values, a media timing function can be expressed as four floating-point values. That is, in fact, how it is expressed.

So, for example, the ease-in-out timing function is expressed as the four values 0.42, 0.0, 0.58, 1.0. That defines a Bézier curve with one endpoint at {0,0}, whose control point is {0.42,0}, and the other endpoint at {1,1}, whose control point is {0.58,1} (Figure 4-1).

An ease-in-out Bézier curve
Figure 4-1. An ease-in-out Bézier curve

To define your own media timing function, supply the coordinates of the two control points by calling functionWithControlPoints:::: or initWithControlPoints::::; this is one of those rare cases where the parameters of an Objective-C method have no name. (It helps to design the curve in a standard drawing program first so that you can visualize how the placement of the control points shapes the curve.) For example, here’s a media timing function that starts out quite slowly and then whips quickly into place after about two-thirds of the time has elapsed. I call this the “clunk” timing function, and it looks great with the compass arrow:

CAMediaTimingFunction* clunk =
    [CAMediaTimingFunction functionWithControlPoints:.9 :.1 :.7 :.9];
[CATransaction setAnimationTimingFunction: clunk];
arrow.transform = CATransform3DRotate(arrow.transform, M_PI/4.0, 0, 0, 1);

Core Animation

Core Animation is the fundamental underlying iOS animation technology. View animation and implicit layer animation are merely convenient façades for Core Animation. Core Animation is explicit layer animation, and revolves primarily around the CA​Animation class and its subclasses, which allow you to create far more elaborate specifications of an animation than anything we’ve encountered so far.

You may never program at the level of Core Animation, but you should read this section anyway, if only to learn how animation really works and to get a sense of its mighty powers. In particular, Core Animation:

  • Works even on a view’s underlying layer. Thus, Core Animation is the only way to apply full-on layer property animation to a view.
  • Provides fine control over the intermediate values and timing of an animation.
  • Allows animations to be grouped into complex combinations.
  • Adds transition animation effects that aren’t available otherwise, such as new content “pushing” the previous content out of a layer.

Warning

Animating a view’s underlying layer with Core Animation is layer animation, not view animation — so you don’t get any automatic layout of that view’s subviews. This can be a reason for preferring view animation.

CABasicAnimation and Its Inheritance

The simplest way to animate a property with Core Animation is with a CABasicAnimation object. CABasicAnimation derives much of its power through its inheritance, so I’ll describe that inheritance along with CABasicAnimation itself. You will readily see that all the property animation features we have met so far are embodied in a CABasicAnimation instance.

CAAnimation

CAAnimation is an abstract class, meaning that you’ll only ever use a subclass of it. Some of CAAnimation’s powers come from its implementation of the CAMediaTiming protocol.

animation
A class method, a convenient way of creating an animation object.
delegate

The delegate messages are animationDidStart: and animationDidStop:​finished:.

A CAAnimation instance retains its delegate; this is very unusual behavior and can cause trouble if you’re not conscious of it (I’m speaking from experience). Alternatively, don’t set a delegate; to make your code run after the animation ends, call the CATransaction class method setCompletionBlock: before configuring the animation.

duration, timingFunction
The length of the animation, and its timing function (a CAMediaTimingFunction). A duration of 0 (the default) means .25 seconds unless overridden by the transaction.
autoreverses, repeatCount, repeatDuration, cumulative
The first two are familiar from UIView animation. The repeatDuration property is a different way to govern repetition, specifying how long the repetition should continue rather than how many repetitions should occur; don’t specify both a repeatCount and a repeatDuration. If cumulative is YES, a repeating animation starts each repetition where the previous repetition ended (rather than jumping back to the start value).
beginTime
The delay before the animation starts. To delay an animation with respect to now, call CACurrentMediaTime and add the desired delay in seconds. The delay does not eat into the animation’s duration.
timeOffset
A shift in the animation’s overall timing; looked at another way, specifies the starting frame of the “animation movie,” which is treated as a loop. For example, an animation with a duration of 8 and a time offset of 4 plays its second half followed by its first half.

CAAnimation, along with all its subclasses, implements KVC to allow you to set and retrieve a value for an arbitrary key, similar to CALayer (Chapter 3) and CATransaction.

CAPropertyAnimation

CAPropertyAnimation is a subclass of CAAnimation. It too is abstract, and adds the following:

keyPath
The all-important string specifying the CALayer key that is to be animated. Recall from Chapter 3 that CALayer properties are accessible through KVC keys; now we are using those keys! A CAPropertyAnimation convenience class method animationWithKeyPath: creates the instance and assigns it a keyPath.
additive
If YES, the values supplied by the animation are added to the current presentation layer value.
valueFunction
Converts a simple scalar value that you supply into a transform.

Warning

There is no animatable CALayer key called @"frame" — because frame is not an animatable layer property. (And the same for affineTransform.)

CABasicAnimation

CABasicAnimation is a subclass (not abstract!) of CAPropertyAnimation. It adds the following:

fromValue, toValue
The starting and ending values for the animation. These values must be objects, so numbers and structs will have to be wrapped accordingly, using NSNumber and NSValue. If neither fromValue nor toValue is provided, the former and current values of the property are used. If just one of fromValue or toValue is provided, the other uses the current value of the property.
byValue
Expresses one of the endpoint values as a difference from the other rather than in absolute terms. So you would supply a byValue instead of a fromValue or instead of a toValue, and the actual fromValue or toValue would be calculated for you by subtraction or addition with respect to the other value. If you supply only a byValue, the fromValue is the property’s current value.

Using a CABasicAnimation

Having constructed and configured a CABasicAnimation, the way you order it to be performed is to add it to a layer. This is done with the CALayer instance method addAnimation:forKey:. (I’ll discuss the purpose of the forKey: parameter later; it’s fine to ignore it and use nil, as I do in the examples that follow.)

However, there’s a slight twist. A CAAnimation is merely an animation; all it does is describe the hoops that the presentation layer is to jump through, the “animation movie” that is to be presented. It has no effect on the layer itself. Thus, if you naively create a CABasicAnimation and add it to a layer with addAnimation:forKey:, the animation happens and then the “animation movie” is whipped away to reveal the layer sitting there in exactly the same state as before. It is up to you to change the layer to match what the animation will ultimately portray.

This requirement may seem odd, but keep in mind that we are now in a much more fundamental, flexible world than the automatic, convenient worlds of view animation and implicit layer animation. Using explicit animation is more work, but you get more power. The converse, as we shall see, is that you don’t have to change the layer if it doesn’t change as a result of the animation.

To assure good results, start by taking a plodding, formulaic approach to the use of CABasicAnimation, like this:

  1. Capture the start and end values for the layer property you’re going to change, because you’re likely to need these values in what follows.
  2. Change the layer property to its end value, first calling setDisableActions: if necessary to prevent implicit animation.
  3. Construct the explicit animation, using the start and end values you captured earlier, and with its keyPath corresponding to the layer property you just changed.
  4. Add the explicit animation to the layer.

Here’s how you’d use this approach to animate our compass arrow rotation:

// capture the start and end values
CATransform3D startValue = arrow.transform;
CATransform3D endValue =
    CATransform3DRotate(startValue, M_PI/4.0, 0, 0, 1);
// change the layer, without implicit animation
[CATransaction setDisableActions:YES];
arrow.transform = endValue;
// construct the explicit animation
CABasicAnimation* anim =
    [CABasicAnimation animationWithKeyPath:@"transform"];
anim.duration = 0.8;
CAMediaTimingFunction* clunk =
    [CAMediaTimingFunction functionWithControlPoints:.9 :.1 :.7 :.9];
anim.timingFunction = clunk;
anim.fromValue = [NSValue valueWithCATransform3D:startValue];
anim.toValue = [NSValue valueWithCATransform3D:endValue];
// ask for the explicit animation
[arrow addAnimation:anim forKey:nil];

Once you know the full form, you will find that in many cases it can be condensed. For example, when fromValue and toValue are not set, the former and current values of the property are used automatically. (This magic is possible because the presentation layer still has the former value of the property, while the layer itself has the new value.) Thus, in this case there was no need to set them, and so there was no need to capture the start and end values beforehand either. Here’s the condensed version:

[CATransaction setDisableActions:YES];
arrow.transform =
    CATransform3DRotate(arrow.transform, M_PI/4.0, 0, 0, 1);
CABasicAnimation* anim =
    [CABasicAnimation animationWithKeyPath:@"transform"];
anim.duration = 0.8;
CAMediaTimingFunction* clunk =
    [CAMediaTimingFunction functionWithControlPoints:.9 :.1 :.7 :.9];
anim.timingFunction = clunk;
[arrow addAnimation:anim forKey:nil];

As I mentioned earlier, you will omit changing the layer if it doesn’t change as a result of the animation. For example, let’s make the compass arrow appear to vibrate rapidly, without ultimately changing its current orientation. To do this, we’ll waggle it back and forth, using a repeated animation, between slightly clockwise from its current position and slightly counterclockwise from its current position. The “animation movie” neither starts nor stops at the current position of the arrow, but for this animation it doesn’t matter, because it all happens so quickly as to appear perfectly natural:

// capture the start and end values
CATransform3D nowValue = arrow.transform;
CATransform3D startValue =
    CATransform3DRotate(nowValue, M_PI/40.0, 0, 0, 1);
CATransform3D endValue =
    CATransform3DRotate(nowValue, -M_PI/40.0, 0, 0, 1);
// construct the explicit animation
CABasicAnimation* anim =
    [CABasicAnimation animationWithKeyPath:@"transform"];
anim.duration = 0.05;
anim.timingFunction =
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim.repeatCount = 3;
anim.autoreverses = YES;
anim.fromValue = [NSValue valueWithCATransform3D:startValue];
anim.toValue = [NSValue valueWithCATransform3D:endValue];
// ask for the explicit animation
[arrow addAnimation:anim forKey:nil];

That code, too, can be shortened considerably from its full form. We can eliminate the need to calculate the new rotation values based on the arrow’s current transform by setting our animation’s additive property to YES; this means that the animation’s property values are added to the existing property value for us, so that they are relative, not absolute. For a transform, “added” means “matrix-multiplied,” so we can describe the waggle without any dependence on the arrow’s current rotation. Moreover, because our rotation is so simple (around a cardinal axis), we can take advantage of CAPropertyAnimation’s valueFunction; the animation’s property values can then be simple scalars (in this case, angles), because the valueFunction tells the animation to interpret these as rotations around the z-axis:

CABasicAnimation* anim =
    [CABasicAnimation animationWithKeyPath:@"transform"];
anim.duration = 0.05;
anim.timingFunction =
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim.repeatCount = 3;
anim.autoreverses = YES;
anim.additive = YES;
anim.valueFunction =
    [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
anim.fromValue = @(M_PI/40);
anim.toValue = @(-M_PI/40);
[arrow addAnimation:anim forKey:nil];

Warning

Instead of using a valueFunction, we could have set the animation’s key path to @"transform.rotation.z" to achieve the same effect. However, Apple advises against this, as it can result in mathematical trouble when there is more than one rotation.

Remember that there is no @"frame" key. To animate a layer’s frame, if both its position and bounds are to change, you must animate both. Recall this earlier example from a view animation’s animations: block, where outer is the superview of inner, and inner expands to fill the width of outer:

CGRect f = self.inner.frame;
f.size.width = self.outer.frame.size.width;
f.origin.x = 0;
self.inner.frame = f;

Here’s how to do that with Core Animation:

CABasicAnimation* anim1 =
    [CABasicAnimation animationWithKeyPath:@"bounds"];
CGRect f = self.inner.layer.bounds;
f.size.width = self.outer.layer.bounds.size.width;
self.inner.layer.bounds = f;
[self.inner.layer addAnimation: anim1 forKey: nil];
CABasicAnimation* anim2 =
    [CABasicAnimation animationWithKeyPath:@"position"];
CGPoint p = self.inner.layer.position;
p.x = CGRectGetMidX(self.outer.layer.bounds);
self.inner.layer.position = p;
[self.inner.layer addAnimation:anim2 forKey: nil];

Keyframe Animation

Keyframe animation (CAKeyframeAnimation) is an alternative to basic animation (CABasicAnimation); they are both subclasses of CAPropertyAnimation and they are used in identical ways. The difference is that a keyframe animation, in addition to specifying a starting and ending value, also specifies multiple values through which the animation should pass on the way, the stages (frames) of the animation. This can be as simple as setting the animation’s values property (an NSArray).

Here’s a more sophisticated version of our animation for waggling the compass arrow: the animation includes both the start and end states, and the degree of waggle gets progressively smaller:

NSMutableArray* values = [NSMutableArray array];
[values addObject: @0.0f];
int direction = 1;
for (int i = 20; i < 60; i += 5, direction *= -1) { // alternate directions
    [values addObject: @(direction*M_PI/(float)i)];
}
[values addObject: @0.0f];
CAKeyframeAnimation* anim =
    [CAKeyframeAnimation animationWithKeyPath:@"transform"];
anim.values = values;
anim.additive = YES;
anim.valueFunction =
    [CAValueFunction functionWithName: kCAValueFunctionRotateZ];
[arrow addAnimation:anim forKey:nil];

Here are some CAKeyframeAnimation properties:

values
The array of values the animation is to adopt, including the starting and ending value.
timingFunctions
An array of timing functions, one for each stage of the animation (so that this array will be one element shorter than the values array).
keyTimes
An array of times to accompany the array of values, defining when each value should be reached. The times start at 0 and are expressed as increasing fractions of 1, ending at 1.
calculationMode

Describes how the values are treated to create all the values through which the animation must pass.

  • The default is kCAAnimationLinear, a simple straight-line interpolation from value to value.
  • kCAAnimationCubic constructs a single smooth curve passing through all the values (and additional advanced properties, tensionValues, continuityValues, and biasValues, allow you to refine the curve).
  • kCAAnimationPaced and kCAAnimationCubicPaced means the timing functions and key times are ignored, and the velocity is made constant through the whole animation.
  • kCAAnimationDiscrete means no interpolation: we jump directly to each value at the corresponding key time.
path
When you’re animating a property whose values are pairs of floats (CGPoints), this is an alternative way of describing the values; instead of a values array, which must be interpolated to arrive at the intermediate values along the way, you supply the entire interpolation as a single CGPathRef. The points used to draw the path are the keyframe values, so you can still apply timing functions and key times. If you’re animating a position, the rotationMode property lets you ask the animated object to rotate so as to remain perpendicular to the path.

In this example, the values array is a sequence of five images to be presented successively and repeatedly in a layer’s contents, like the frames in a movie; the effect is similar to UIImageView and UIImage animation, discussed earlier in this chapter:

CAKeyframeAnimation* anim =
    [CAKeyframeAnimation animationWithKeyPath:@"contents"];
anim.values = self.images;
anim.keyTimes = @[@0,@0.25,@0.5,@0.75,@1];
anim.calculationMode = kCAAnimationDiscrete;
anim.duration = 1.5;
anim.repeatCount = HUGE_VALF;
[self.sprite addAnimation:anim forKey:nil];

Making a Property Animatable

So far, we’ve been animating built-in animatable properties. If you define your own property on a CALayer subclass, you can make that property animatable through a CAPropertyAnimation (a CABasicAnimation or a CAKeyframeAnimation). For example, here we animate the increase and decrease in a CALayer subclass property called thickness:

CALayer* lay = self.v.layer;
CABasicAnimation* ba =
    [CABasicAnimation animationWithKeyPath:@"thickness"];
ba.toValue = [NSNumber numberWithFloat: 10.0];
ba.autoreverses = YES;
[lay addAnimation:ba forKey:nil];

To make our layer responsive to such a command, it needs a thickness property declared @dynamic (so that Core Animation can create its accessors), and it must return YES from the class method needsDisplayForKey:, where the key is the string name of the property:

@interface MyLayer ()
@property CGFloat thickness;
@end
@implementation MyLayer
@dynamic thickness;
+ (BOOL) needsDisplayForKey:(NSString *)key {
    if ([key isEqualToString: @"thickness"])
        return YES;
    return [super needsDisplayForKey:key];
}
// ...
@end

Returning YES from needsDisplayForKey: causes this layer to be redisplayed repeatedly as the thickness property changes. So if we want to see the animation, this layer also needs to draw itself in some way that depends on the thickness property. Here, I’ll implement the layer’s drawInContext: to make thickness the thickness of the black border around a red rectangle:

- (void) drawInContext:(CGContextRef)con {
    CGRect r = CGRectInset(self.bounds, 20, 20);
    CGContextSetFillColorWithColor(con, [UIColor redColor].CGColor);
    CGContextFillRect(con, r);
    CGContextSetLineWidth(con, self.thickness);
    CGContextStrokeRect(con, r);
}

At every step of the animation, drawInContext: is called, and because the thickness value differs at each step, it appears animated.

Grouped Animations

A grouped animation (CAAnimationGroup) combines multiple animations into one, by means of its animations property (an NSArray of animations). By delaying and timing the various component animations, complex effects can be achieved.

A CAAnimationGroup is itself an animation; it is a CAAnimation subclass, so it has a duration and other animation features. Think of the CAAnimationGroup as the parent and its animations as its children. Then the children inherit default values from their parent. Thus, for example, if you don’t set a child’s duration explicitly, it will inherit the parent’s duration. Also, make sure the parent’s duration is sufficient to include all parts of the child animations that you want displayed.

Let’s use a grouped animation to construct a sequence where the compass arrow rotates and then waggles. This requires very little modification of code we’ve already written. We express the first animation in its full form, with explicit fromValue and toValue. We postpone the second animation using its beginTime property; notice that we express this in relative terms, as a number of seconds into the parent’s duration, not with respect to CACurrentMediaTime. Finally, we set the overall parent duration to the sum of the child durations, so that it can embrace both of them:

// capture current value, set final value
CGFloat rot = M_PI/4.0;
[CATransaction setDisableActions:YES];
CGFloat current =
    [[arrow valueForKeyPath:@"transform.rotation.z"] floatValue];
[arrow setValue: @(current + rot)
       forKeyPath:@"transform.rotation.z"];
// first animation (rotate and clunk)
CABasicAnimation* anim1 =
    [CABasicAnimation animationWithKeyPath:@"transform"];
anim1.duration = 0.8;
CAMediaTimingFunction* clunk =
    [CAMediaTimingFunction functionWithControlPoints:.9 :.1 :.7 :.9];
anim1.timingFunction = clunk;
anim1.fromValue = @(current);
anim1.toValue = @(current + rot);
anim1.valueFunction =
    [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
// second animation (waggle)
NSMutableArray* values = [NSMutableArray array];
[values addObject: @0.0f];
int direction = 1;
for (int i = 20; i < 60; i += 5, direction *= -1) { // alternate directions
    [values addObject: @(direction*M_PI/(float)i)];
}
[values addObject: @0.0f];
CAKeyframeAnimation* anim2 =
    [CAKeyframeAnimation animationWithKeyPath:@"transform"];
anim2.values = values;
anim2.duration = 0.25;
anim2.beginTime = anim1.duration;
anim2.additive = YES;
anim2.valueFunction =
    [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
// group
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = @[anim1, anim2];
group.duration = anim1.duration + anim2.duration;
[arrow addAnimation:group forKey:nil];

In that example, I grouped two animations that animated the same property sequentially. Now let’s go to the other extreme and group some animations that animate different properties simultaneously. I have a small view (self.v), located near the top-right corner of the screen, whose layer contents are a picture of a sailboat facing to the left. I’ll “sail” the boat in a curving path, both down the screen and left and right across the screen, like an extended letter “S” (Figure 4-2). Each time the boat comes to a vertex of the curve, changing direction across the screen, I’ll turn the boat picture so that it faces the way it’s about to move. At the same time, I’ll constantly rock the boat, so that it always appears to be pitching a little on the waves.

A boat and the course she’ll sail
Figure 4-2. A boat and the course she’ll sail

Here’s the first animation, the movement of the boat along its curving path. It illustrates the use of a CAKeyframeAnimation with a CGPath; the calculationMode of kCAAnimationPaced ensures an even speed over the whole path. We don’t set an explicit duration because we want to adopt the duration of the group:

CGFloat h = 200;
CGFloat v = 75;
CGMutablePathRef path = CGPathCreateMutable();
int leftright = 1;
CGPoint next = self.v.layer.position;
CGPoint pos;
CGPathMoveToPoint(path, nil, next.x, next.y);
for (int i = 0; i < 4; i++) {
    pos = next;
    leftright *= -1;
    next = CGPointMake(pos.x+h*leftright, pos.y+v);
    CGPathAddCurveToPoint(path, nil, pos.x, pos.y+30, next.x, next.y-30,
                          next.x, next.y);
}
CAKeyframeAnimation* anim1 =
    [CAKeyframeAnimation animationWithKeyPath:@"position"];
anim1.path = path;
anim1.calculationMode = kCAAnimationPaced;

Here’s the second animation, the reversal of the direction the boat is facing. This is simply a rotation around the y-axis. It’s another CAKeyframeAnimation, but we make no attempt at visually animating this reversal: the calculationMode is kCAAnimationDiscrete, so that the boat image reversal is a sudden change, as in our earlier “sprite” example. There is one less value than the number of points in our first animation’s path, and the first animation has an even speed, so the reversals take place at each curve apex with no further effort on our part. (If the pacing were more complicated, we could give both the first and the second animation identical keyTimes arrays, to coordinate them.) Once again, we don’t set an explicit duration:

NSArray* revs = @[@0.0f,
                  @M_PI,
                  @0.0f,
                  @M_PI];
CAKeyframeAnimation* anim2 =
    [CAKeyframeAnimation animationWithKeyPath:@"transform"];
anim2.values = revs;
anim2.valueFunction =
    [CAValueFunction functionWithName:kCAValueFunctionRotateY];
anim2.calculationMode = kCAAnimationDiscrete;

Here’s the third animation, the rocking of the boat. It has a short duration, and repeats indefinitely (by giving its repeatCount an immense value):

NSArray* pitches = @[@0.0f,
                     @(M_PI/60.0),
                     @0.0f,
                     @(-M_PI/60.0),
                     @0.0f];
CAKeyframeAnimation* anim3 =
    [CAKeyframeAnimation animationWithKeyPath:@"transform"];
anim3.values = pitches;
anim3.repeatCount = HUGE_VALF;
anim3.duration = 0.5;
anim3.additive = YES;
anim3.valueFunction =
    [CAValueFunction functionWithName:kCAValueFunctionRotateZ];

Finally, we combine the three animations, assigning the group an explicit duration that will be adopted by the first two animations. As we hand the animation over to the layer displaying the boat, we also change the layer’s position to match the final position from the first animation, so that the boat won’t jump back to its original position afterward:

CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = @[anim1, anim2, anim3];
group.duration = 8;
[self.v.layer addAnimation:group forKey:nil];
[CATransaction setDisableActions:YES];
self.v.layer.position = next;

Here are some further CAAnimation properties (from the CAMediaTiming protocol) that come into play especially when animations are grouped:

speed
The ratio between a child’s timescale and the parent’s timescale. For example, if a parent and child have the same duration, but the child’s speed is 1.5, its animation runs one-and-a-half times as fast as the parent.
fillMode

Suppose the child animation begins after the parent animation, or ends before the parent animation, or both. What should happen to the appearance of the property being animated, outside the child animation’s boundaries? The answer depends on the child’s fillMode:

  • kCAFillModeRemoved means the child animation is removed, revealing the layer property at its actual current value whenever the child is not running.
  • kCAFillModeForwards means the final presentation layer value of the child animation remains afterward.
  • kCAFillModeBackwards means the initial presentation layer value of the child animation appears right from the start.
  • kCAFillModeBoth combines the previous two.

Note

CALayer adopts the CAMediaTiming protocol. Thus, a layer can have a speed. This will affect any animation attached to it. A CALayer with a speed of 2 will play a 10-second animation in 5 seconds. A layer can also have a timeOffset; changing a layer’s timeOffset effectively changes what “frame” of its animation is displayed.

Transitions

A layer transition is an animation involving two “copies” of a single layer, in which the second “copy” appears to replace the first. It is described by an instance of CATransition (a CAAnimation subclass), which has these chief properties describing the animation:

type

Your choices are:

  • kCATransitionFade
  • kCATransitionMoveIn
  • kCATransitionPush
  • kCATransitionReveal
subtype

If the type is not kCATransitionFade, your choices are:

  • kCATransitionFromRight
  • kCATransitionFromLeft
  • kCATransitionFromTop
  • kCATransitionFromBottom

Note

For historical reasons, the terms “bottom” and “top” in the names of the subtype settings have the opposite of their expected meanings.

To understand a layer transition, first implement one without changing anything else about the layer:

CATransition* t = [CATransition animation];
t.type = kCATransitionPush;
t.subtype = kCATransitionFromBottom;
[layer addAnimation: t forKey: nil];

The entire layer exits moving down from its original place, and another copy of the very same layer enters moving down from above. If, at the same time, we change something about the layer’s contents, then the old contents will appear to exit downward while the new contents appear to enter from above.

A common device is to have the layer that is to be transitioned live inside a superlayer that is exactly the same size and whose masksToBounds is YES. This confines the visible transition to the bounds of the layer itself. Otherwise, the entering and exiting versions of the layer are visible outside the layer.

In this example, we change the sublayer’s contents image, while transitioning it with a push transition from the bottom (meaning from the top), from an image of Mars to a smiley face; with the sublayer masked by its superlayer, it looks as if the smiley face pushes Mars down out of the frame (which I’ve emphasized in Figure 4-3 by giving the superlayer a border as well):

CATransition* t = [CATransition animation];
t.type = kCATransitionPush;
t.subtype = kCATransitionFromBottom;
t.duration = 2;
[CATransaction setDisableActions:YES];
lay.contents = (id)[UIImage imageNamed: @"Smiley"].CGImage;
[lay addAnimation: t forKey: nil];
A push transition
Figure 4-3. A push transition

A transition on a superlayer can happen simultaneously with animation of a sublayer. The animation will be seen to occur on the second “copy” of the layer as it moves into position. This is analogous to what we achieved earlier with the UIViewAnimationOptionAllowAnimatedContent option using block-based view animation.

Animations List

The method that asks for an explicit animation to happen is CALayer’s addAnimation:forKey:. To understand how this method actually works (and what the “key” is), you need to know about a layer’s animations list.

An animation is an object (a CAAnimation) that modifies how a layer is drawn. It does this merely by being attached to the layer; the layer’s drawing mechanism does the rest. A layer maintains a list of animations that are currently in force. To add an animation to this list, you call addAnimation:forKey:. When the time comes to draw itself, the layer looks through its animations list and draws itself in accordance with any animations it finds there. (The list of things the layer must do in order to draw itself is sometimes referred to by the documentation as the render tree.)

The animations list is maintained in a curious way. The list is not exactly a dictionary, but it behaves somewhat like a dictionary. An animation has a key — the forKey: parameter in addAnimation:forKey:. If an animation with a certain key is added to the list, and an animation with that key is already in the list, the one that is already in the list is removed. Thus a rule is maintained that only one animation with a given key can be in the list at a time (the exclusivity rule). This explains why sometimes ordering an animation can cancel an animation already ordered or in-flight: the two animations had the same key, so the first one was removed. It is also possible to add an animation with no key (the key is nil); it is then not subject to the exclusivity rule (that is, there can be more than one animation in the list with no key). The order in which animations were added to the list is the order in which they are applied.

The forKey: parameter in addAnimation:forKey: is thus not a property name. It could be a property name, but it can be any arbitrary value. Its purpose is to enforce the exclusivity rule. It does not have any meaning with regard to what property a CAPropertyAnimation animates; that is the job of the animation’s keyPath. (Apple’s use of the term “key” in addAnimation:forKey: is thus unfortunate and misleading; I wish they had named this method addAnimation:withIdentifier: or something like that.)

Note

Actually, there is a relationship between the “key” in addAnimation:forKey: and a CAPropertyAnimation’s keyPath — if a CAPropertyAnimation’s keyPath is nil at the time that it is added to a layer with addAnimation:forKey:, that keyPath is set to the forKey: value. Thus, you can misuse the forKey: parameter in addAnimation:forKey: as a way of specifying what keyPath an animation animates. (This fact is not documented, so far as I know, but it’s easily verified experimentally, and it should remain reliably true, as implicit animation crucially depends on it.) I have seen many prominent but misleading examples that use this technique, apparently in the mistaken belief that the “key” in addAnimation:forKey: is the way you are supposed to specify what property to animate. This is wrong. Set the CAPropertyAnimation’s keyPath explicitly (as do all my examples); that’s what it’s for.

You can use the exclusivity rule to your own advantage, to keep your code from stepping on its own feet. Some code of yours might add an animation to the list using a certain key; then later, some other code might come along and correct this, removing that animation and replacing it with another. By using the same key, the second code is easily able to override the first: “You may have been given some other animation with this key, but throw it away; play this one instead.”

In some cases, the key you supply is ignored and a different key is substituted. In particular, the key with which a CATransition is added to the list is always kCATransition (which happens to be @"transition"); thus there can be only one transition animation in the list.

You can think of an animation in a layer’s animations list as being the “animation movie” I spoke of at the start of this chapter. As long as an animation is in the list, the movie is present, either waiting to be played or actually playing. An animation that has finished playing is, in general, pointless; the animation should now be removed from the list. Therefore, an animation has a removedOnCompletion property, which defaults to YES: when the “movie” is over, the animation removes itself from the list.

You can, if desired, set removedOnCompletion to NO. However, even the presence in the list of an animation that has already played might make no difference to the layer’s appearance, because an animation’s fillMode is kCAFillModeRemoved, which removes the animation from the layer’s drawing when the movie is over. Thus, it can usually do no harm to leave an animation in the list after it has played, but it’s not a great idea either, because this is just one more thing for the drawing system to worry about. Typically, you’ll leave removedOnCompletion set at YES.

Note

You may encounter examples that set removedOnCompletion to NO and set the animation’s fillMode to kCAFillModeForwards or kCAFillModeBoth, as a way of causing the layer to keep the appearance of the last frame of the “animation movie” even after the animation is over, and preventing a property from apparently jumping back to its initial value when the animation ends. This is wrong. The correct approach, as I have explained, is to change the property value to match the final frame of the animation. The proper use of kCAFillModeForwards is in connection with a child animation within a grouped animation.

You can’t access the entire animations list directly. You can access the key names of the animations in the list, with animationKeys; and you can obtain or remove an animation with a certain key, with animationForKey: and removeAnimationForKey:; but animations with a nil key are inaccessible. You can, however, remove all animations, including animations with a nil key, using removeAllAnimations. When your app is suspended, removeAllAnimations is called on all layers for you; that is why it is possible to suspend an app coherently in the middle of an animation.

If an animation is in-flight when you remove it from the animations list manually, by calling removeAllAnimations or removeAnimationForKey:, it will stop; however, that doesn’t happen until the next redraw moment. You might be able to work around this, if you need an animation to be removed immediately, by wrapping the remove... call in an explicit transaction block.

Actions

For the sake of completeness, I will now explain how implicit animation really works — that is, how implicit animation is turned into explicit animation behind the scenes. The basis of implicit animation is the action mechanism.

What an Action Is

An action is an object that adopts the CAAction protocol. This means simply that it implements runActionForKey:object:arguments:. The action object could do anything in response to this message. The notion of an action is completely general. However, in real life, the only class that adopts the CAAction protocol is CAAnimation.

You would never send runActionForKey:object:arguments: to an animation directly. Rather, this message is sent to an animation for you, as the basis of implicit animation. The key is the property that you set, and the object is the layer whose property you set.

What an animation does when it receives runActionForKey:​object:arguments: is to assume that the second parameter, the object:, is a layer, and to add itself to that layer’s animations list. Thus, for an animation, receiving the runActionForKey:object:arguments: message is like being told: “Play yourself!”

This is where the rule comes into play, which I mentioned earlier, that if an animation’s keyPath is nil, the key by which the animation is assigned to a layer’s animations list is used as the keyPath. When an animation is sent runActionForKey:​object:arguments:, it responds by calling [object addAnimation:self forKey:key], where the key is the name of the property that was set. The animation’s keyPath for an implicit layer animation is in fact usually nil, so this call also sets the animations keyPath to the same key! That is how the property that you set ends up being the property that is animated.

When you set a property of a layer and trigger an implicit animation, you are actually triggering the action search. This basically means that the layer searches for an action object to which it can send the runActionForKey:object:arguments: message; because that action object will be an animation, and because it will respond to this message by adding itself to the layer’s animations list, this is the same as saying that the layer searches for an animation to play itself with respect to the layer. The procedure by which the layer searches for this animation is quite elaborate.

The search for an action object begins because you do something that causes the layer to be sent the actionForKey: message. Let us presume that what you do is to change the value of an animatable property. (Other things can cause the actionForKey: message to be sent, as I’ll show later.) The action mechanism then treats the name of the property as a key, and the layer receives actionForKey: with that key — and the action search begins.

At each stage of the action search, the following rules are obeyed regarding what is returned from that stage of the search:

An action object
If an action object (an animation) is produced, that is the end of the search. The action mechanism sends that animation the runActionForKey:object:​arguments: message; the animation responds by adding itself to the layer’s animations list.
[NSNull null]
If [NSNull null] is produced, that is the end of the search. There will be no implicit animation; [NSNull null] means, “Do nothing and stop searching.”
nil
If nil is produced, the search continues to the next stage.

The action search proceeds by stages, as follows:

  1. The layer’s actionForKey: might terminate the search before it even starts. For example, the layer will do this if it is the underlying layer of a view, or if a property is set to the same value it already has. In such a case, there should be no implicit animation, so the whole mechanism is nipped in the bud. (This stage is special in that a returned value of nil ends the search and no animation takes place.)
  2. If the layer has a delegate that implements actionForLayer:forKey:, that message is sent to the delegate, with this layer as the layer and the property name as the key. If an animation or [NSNull null] is returned, the search ends.
  3. The layer has a property called actions, which is a dictionary. If there is an entry in this dictionary with the given key, that value is used, and the search ends.
  4. The layer has a property called style, which is a dictionary. If there is an entry in this dictionary with the key actions, it is assumed to be a dictionary; if this actions dictionary has an entry with the given key, that value is used, and the search ends. Otherwise, if there is an entry in the style dictionary called style, the same search is performed within it, and so on recursively until either an actions entry with the given key is found (the search ends) or there are no more style entries (the search continues).

    (If the style dictionary sounds profoundly weird, that’s because it is profoundly weird. It is actually a special case of a larger, separate mechanism, which is also profoundly weird, having to do not with actions, but with a CALayer’s implementation of KVC. When you call valueForKey: on a layer, if the key is undefined by the layer itself, the style dictionary is consulted. I have never written or seen code that uses this mechanism for anything, and I’ll say no more about it.)

  5. The layer’s class is sent defaultActionForKey:, with the property name as the key. If an animation or [NSNull null] is returned, the search ends.
  6. If the search reaches this last stage, a default animation is supplied, as appropriate. For a property animation, this is a plain vanilla CABasicAnimation.

Both the delegate’s actionForLayer:forKey: and the subclass’s defaultActionForKey: are declared as returning an id<CAAction>. To return [NSNull null], therefore, you’ll need to typecast it to id<CAAction> to quiet the compiler; you’re lying (NSNull does not adopt the CAAction protocol), but it doesn’t matter.

You can affect the action search at various stages to modify what happens when the search is triggered. Perhaps the most common real-life case is to turn off implicit animation for some particular property. This is done by returning nil from actionForKey: itself, in a CALayer subclass; this suppresses the action search altogether. Here’s the code from a CALayer subclass that doesn’t animate its position property (but does animate its other properties normally):

-(id<CAAction>)actionForKey:(NSString *)event {
    if ([event isEqualToString:@"position"])
        return nil;
    return [super actionForKey:event];
}

For more flexibility, we can take advantage of the fact that a CALayer acts like a dictionary (allowing us to set an arbitrary key’s value) — we’ll embed a switch in our CALayer subclass that we can use to turn implicit position animation on and off at will:

-(id<CAAction>)actionForKey:(NSString *)event {
    if ([event isEqualToString:@"position"] &&
            [self valueForKey:@"suppressPositionAnimation"])
        return nil;
    return [super actionForKey:event];
}

To turn off implicit position animation for an instance of this layer, we set its @"suppressPositionAnimation" key to a non-nil value:

[layer setValue:@YES forKey:@"suppressPositionAnimation"];

Assuming now that the action search is permitted, you could cause some stage of the search to produce an animation of your own; that animation will then be used. Assuming that the search is triggered by setting an animatable layer property, you would then be affecting how implicit animation behaves.

You will probably want your animation to be fairly minimal. You may have no way of knowing the former and current values of the property that is being changed, so it would then be pointless (and very strange) to set a CABasicAnimation’s fromValue or toValue. Moreover, although animation properties that you don’t set can be set through CATransaction, in the usual manner for implicit property animation, animation properties that you do set can not be overridden through CATransaction. For example, if you set the duration of the animation that you produce at some stage of the action search, a call to CATransaction’s setAnimationDuration: cannot change it.

Let’s say we want a certain layer’s duration for an implicit position animation to be 5 seconds. We can achieve this with a minimally configured animation, like this:

CABasicAnimation* ba = [CABasicAnimation animation];
ba.duration = 5;

The idea now is to situate this animation, ba, where it will be produced by the action search when implicit animation is triggered on the position property of our layer. We could, for instance, put it into the layer’s actions dictionary:

layer.actions = @{@"position": ba};

The result is that when we set that layer’s position, if an implicit animation results, its duration is 5 seconds, even if we try to change it through CATransaction:

[CATransaction setAnimationDuration:1];
layer.position = CGPointMake(100,100); // animation takes 5 seconds

Using the layer’s actions dictionary to set default animations is a somewhat inflexible way to hook into the action search, however. It has the disadvantage in general that you must write your animation beforehand. By contrast, if you set the layer’s delegate to an instance that responds to actionForLayer:forKey:, your code runs at the time the animation is needed, and you have access to the layer that is to be animated. So you can create the animation on the fly, possibly modifying it in response to current circumstances.

Moreover, CATransaction (like CALayer) implements KVC to allow you to set and retrieve the value of arbitrary keys. We can take advantage of this fact to pass additional information from the code that sets the property value, and triggers the action search, to the code that supplies the action (because they both run within the same transaction).

In this example, we use the layer delegate to change the default position animation so that the path, instead of being a straight line, has a slight waggle. To do this, the delegate constructs a keyframe animation. The animation depends on the old position value and the new position value; the delegate can get the former direct from the layer, but the latter must be handed to the delegate somehow. Here, a CATransaction key @"newP" is used to communicate this information. When we set the layer’s position, we put its future value where the delegate can retrieve it, like this:

CGPoint newP = CGPointMake(300,300);
[CATransaction setValue: [NSValue valueWithCGPoint: newP] forKey: @"newP"];
layer.position = newP;

The delegate is called by the action search and constructs the animation:

- (id < CAAction >)actionForLayer:(CALayer *)layer forKey:(NSString *)key {
    if ([key isEqualToString: @"position"]) {
        CGPoint oldP = layer.position;
        CGPoint newP = [[CATransaction valueForKey: @"newP"] CGPointValue];
        CGFloat d = sqrt(pow(oldP.x - newP.x, 2) + pow(oldP.y - newP.y, 2));
        CGFloat r = d/3.0;
        CGFloat theta = atan2(newP.y - oldP.y, newP.x - oldP.x);
        CGFloat wag = 10*M_PI/180.0;
        CGPoint p1 = CGPointMake(oldP.x + r*cos(theta+wag),
                                 oldP.y + r*sin(theta+wag));
        CGPoint p2 = CGPointMake(oldP.x + r*2*cos(theta-wag),
                                 oldP.y + r*2*sin(theta-wag));
        CAKeyframeAnimation* anim = [CAKeyframeAnimation animation];
        anim.values = @[
                       [NSValue valueWithCGPoint:oldP],
                       [NSValue valueWithCGPoint:p1],
                       [NSValue valueWithCGPoint:p2],
                       [NSValue valueWithCGPoint:newP]
                       ];
        anim.calculationMode = kCAAnimationCubic;
        return anim;
    }
    return nil;
}

Finally, I’ll demonstrate overriding defaultActionForKey:. This code would go into a CALayer subclass; setting this layer’s contents will now automatically trigger a push transition from the left:

+ (id < CAAction >)defaultActionForKey:(NSString *)aKey {
    if ([aKey isEqualToString:@"contents"]) {
        CATransition* tr = [CATransition animation];
        tr.type = kCATransitionPush;
        tr.subtype = kCATransitionFromLeft;
        return tr;
    }
    return [super defaultActionForKey: aKey];
}

Nonproperty Actions

Changing a property is not the only way to trigger a search for an action; an action search is also triggered when a layer is added to a superlayer (key kCAOnOrderIn) and when a layer’s sublayers are changed by adding or removing a sublayer (key @"sublayers").

Warning

These triggers and their keys are incorrectly described in Apple’s documentation (and headers).

In this example, we use our layer’s delegate so that when our layer is added to a superlayer, it will “pop” into view. We implement this by fading the layer quickly in from an opacity of 0 and at the same time scaling the layer’s transform to make it momentarily appear a little larger:

- (id < CAAction >)actionForLayer:(CALayer *)lay forKey:(NSString *)key {
    if ([key isEqualToString:kCAOnOrderIn]) {
        CABasicAnimation* anim1 =
            [CABasicAnimation animationWithKeyPath:@"opacity"];
        anim1.fromValue = @0.0f;
        anim1.toValue = @(lay.opacity);
        CABasicAnimation* anim2 =
            [CABasicAnimation animationWithKeyPath:@"transform"];
        anim2.toValue =
            [NSValue valueWithCATransform3D:
                CATransform3DScale(lay.transform, 1.1, 1.1, 1.0)];
        anim2.autoreverses = YES;
        anim2.duration = 0.1;
        CAAnimationGroup* group = [CAAnimationGroup animation];
        group.animations = @[anim1, anim2];
        group.duration = 0.2;
        return group;
    }
}

The documentation says that when a layer is removed from a superlayer, an action is sought under the key kCAOnOrderOut. This is true but useless, because by the time the action is sought, the layer has already been removed from the superlayer, so returning an animation has no visible effect. Similarly, an animation returned as an action when a layer’s hidden is set to YES is never played. A possible workaround is to trigger the animation via the opacity property, perhaps in conjunction with a CATransaction key functioning as a switch, and remove the layer afterward:

[CATransaction setCompletionBlock: ^{
    [layer removeFromSuperlayer];
}];
[CATransaction setValue:@YES forKey:@"byebye"];
layer.opacity = 0;

Now the delegate’s actionForLayer:forKey: can test for the incoming key @"opacity" and the CATransaction key @"byebye", and return the animation appropriate to removal from the superlayer. Here’s a possible implementation:

if ([key isEqualToString:@"opacity"]) {
    if ([CATransaction valueForKey:@"byebye"]) {
        CABasicAnimation* anim1 =
            [CABasicAnimation animationWithKeyPath:@"opacity"];
        anim1.fromValue = @(layer.opacity);
        anim1.toValue = @0.0f;
        CABasicAnimation* anim2 =
            [CABasicAnimation animationWithKeyPath:@"transform"];
        anim2.toValue =
            [NSValue valueWithCATransform3D:
                CATransform3DScale(layer.transform, 0.1, 0.1, 1.0)];
        CAAnimationGroup* group = [CAAnimationGroup animation];
        group.animations = @[anim1, anim2];
        group.duration = 0.2;
        return group;
    }
}

Emitter Layers

Emitter layers (CAEmitterLayer) are, to some extent, on a par with animated images: once you’ve set up an emitter layer, it just sits there animating all by itself. The nature of this animation is rather narrow: an emitter layer emits particles, which are CAEmitterCell instances. However, by clever setting of the properties of an emitter layer and its emitter cells, you can achieve some astonishing effects. Moreover, the animation is itself animatable using Core Animation.

Here are some useful basic properties of a CAEmitterCell:

contents, contentsRect
These are modeled after the eponymous CALayer properties, although CAEmitterLayer is not a CALayer subclass; so, respectively, an image (a CGImageRef) and a CGRect specifying a region of that image. They define the image that a cell will portray.
birthrate, lifetime
How many cells per second should be emitted, and how many seconds each cell should live before vanishing, respectively.
velocity
The speed at which a cell moves. The unit of measurement is not documented; perhaps it’s points per second.
emissionLatitude, emissionLongitude
The angle at which the cell is emitted from the emitter, as a variation from the perpendicular. Longitude is an angle within the plane; latitude is an angle out of the plane.

So, here’s code to create a very elementary emitter cell:

// make a gray circle image
UIGraphicsBeginImageContextWithOptions(CGSizeMake(10,10), NO, 1);
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con, CGRectMake(0,0,10,10));
CGContextSetFillColorWithColor(con, [UIColor grayColor].CGColor);
CGContextFillPath(con);
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// make a cell with that image
CAEmitterCell* cell = [CAEmitterCell emitterCell];
cell.birthRate = 5;
cell.lifetime = 1;
cell.velocity = 100;
cell.contents = (id)im.CGImage;

(In the first line, we deliberately don’t double the scale on a double-resolution screen, because a CAEmitterLayer has no contentsScale, like a CALayer; we’re going to derive a CGImage from this image, and we don’t want its size doubled.)

The result is that little gray circles should be emitted slowly and steadily, five per second, each one vanishing in one second. Now we need an emitter layer from which these circles are to be emitted. Here are some basic CAEmitterLayer properties (beyond those it inherits from CALayer); these define an imaginary object, an emitter, that will be producing the emitter cells:

emitterPosition
The point at which the emitter should located, in superlayer coordinates. You can optionally add a third dimension to this point, emitterZPosition.
emitterSize
The size of the emitter.
emitterShape

The shape of the emitter. The dimensions of the shape depend on the emitter’s size; the cuboid shape depends also on a third size dimension, emitterDepth. Your choices are:

  • kCAEmitterLayerPoint
  • kCAEmitterLayerLine
  • kCAEmitterLayerRectangle
  • kCAEmitterLayerCuboid
  • kCAEmitterLayerCircle
  • kCAEmitterLayerSphere
emitterMode

The region of the shape from which cells should be emitted. Your choices are:

  • kCAEmitterLayerPoints
  • kCAEmitterLayerOutline
  • kCAEmitterLayerSurface
  • kCAEmitterLayerVolume

Let’s start with the simplest possible case, a single point emitter:

CAEmitterLayer* emit = [CAEmitterLayer new];
emit.emitterPosition = CGPointMake(30,100);
emit.emitterShape = kCAEmitterLayerPoint;
emit.emitterMode = kCAEmitterLayerPoints;

We tell the emitter what types of cell to emit by assigning those cells to its emitterCells property (an array of CAEmitterCell). We then add the emitter to our interface, and presto, it starts emitting:

emit.emitterCells = @[cell];
[self.view.layer addSublayer:emit];

The result is a constant stream of gray circles emitted from the point {30,100}, each circle marching steadily to the right and vanishing after one second (Figure 4-4).

A really boring emitter layer
Figure 4-4. A really boring emitter layer

Now that we’ve succeeded in creating a boring emitter layer, we can start to vary some parameters. The emissionRange defines a cone in which cells will be emitted; if we increase the birthRate and widen the emissionRange, we get something that looks like a stream shooting from a water hose:

cell.birthRate = 100;
cell.lifetime = 1.5;
cell.velocity = 100;
cell.emissionRange = M_PI/5.0;

In addition, as the cell moves, it can be made to accelerate (or decelerate) in each dimension, using its xAcceleration, yAcceleration, and zAcceleration properties. Here, we turn the stream into a falling cascade, like a waterfall coming from the left:

cell.xAcceleration = -40;
cell.yAcceleration = 200;

All aspects of cell behavior can be made to vary randomly, using the following CAEmitterCell properties:

lifetimeRange, velocityRange
How much the lifetime and velocity values are allowed to vary randomly for different cells.
scale
scaleRange, scaleSpeed
The scale alters the size of the cell; the range and speed determine how far and how rapidly this size alteration is allowed to change over the lifetime of each cell.
color
redRange, greenRange, blueRange, alphaRange
redSpeed, greenSpeed, blueSpeed, alphaSpeed
The color is painted in accordance with the opacity of the cell’s contents image; it combines with the image’s color, so if we want the color stated here to appear in full purity, our contents image should use only white. The range and speed determine how far and how rapidly each color component is to change.
spin, spinRange
The spin is a rotational speed (in radians per second); its range determines how far this speed is allowed to change over the lifetime of each cell.

Here we add some variation so that the circles behave a little more independently of one another. Some live longer than others, some come out of the emitter faster than others. And they all start out a shade of blue, but change to a shade of green about half-way through the stream (Figure 4-5):

cell.lifetimeRange = .4;
cell.velocityRange = 20;
cell.scaleRange = .2;
cell.scaleSpeed = .2;
cell.color = [UIColor blueColor].CGColor;
cell.greenRange = .5;
cell.greenSpeed = .75;
An emitter layer that makes a sort of waterfall
Figure 4-5. An emitter layer that makes a sort of waterfall

Once the emitter layer is in place and animating, you can change its parameters and the parameters of its emitter cells through key–value coding on the emitter layer. You can access the emitter cells through the emitter layer’s @"emitterCells" key path; to specify a cell type, use its name property (which you’ll have to have assigned earlier) as the next piece of the key path. For example, suppose we’ve set cell.name to @"circle"; now we’ll change the cell’s greenSpeed so that each cell changes from blue to green much earlier in its lifetime:

[emit setValue:@3.0f
    forKeyPath:@"emitterCells.circle.greenSpeed"];

The significance of this is that such changes can themselves be animated! Here, we’ll attach to the emitter layer a repeating animation that causes our cell’s greenSpeed to move back and forth between two values. The result is that the stream varies, over time, between being mostly blue and mostly green:

NSString* key = @"emitterCells.circle.greenSpeed";
CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:key];
ba.fromValue = @-1.0f;
ba.toValue = @3.0f;
ba.duration = 4;
ba.autoreverses = YES;
ba.repeatCount = HUGE_VALF;
[emit addAnimation:ba forKey:nil];

A CAEmitterCell can itself function as an emitter — that is, it can have cells of its own. Both CAEmitterLayer and CAEmitterCell conform to the CAMediaTiming protocol, and their beginTime and duration properties can be used to govern their times of operation, much as in a grouped animation. For example, this code causes our existing waterfall to spray tiny droplets in the region of the “nozzle” (the emitter):

CAEmitterCell* cell2 = [CAEmitterCell emitterCell];
cell.emitterCells = @[cell2];
cell2.contents = (id)im.CGImage;
cell2.emissionRange = M_PI;
cell2.birthRate = 200;
cell2.lifetime = 0.4;
cell2.velocity = 200;
cell2.scale = 0.2;
cell2.beginTime = .04;
cell2.duration = .2;

But if we change the beginTime to be larger (hence later), the tiny droplets happen near the bottom of the cascade. We must also increase the duration, or stop setting it altogether, since if the duration is less than the beginTime, no emission takes place at all (Figure 4-6):

cell2.beginTime = .7;
cell2.duration = .8;
The waterfall makes a kind of splash
Figure 4-6. The waterfall makes a kind of splash

We can also alter the picture by changing the behavior of the emitter itself. This change turns the emitter into a line, so that our cascade becomes broader:

emit.emitterPosition = CGPointMake(100,25);
emit.emitterSize = CGSizeMake(100,100);
emit.emitterShape = kCAEmitterLayerLine;
emit.emitterMode = kCAEmitterLayerOutline;
cell.emissionLongitude = 3*M_PI/4;

There’s more to know about emitter layers and emitter cells, but at this point you know enough to understand Apple’s sample code simulating such things as fire and smoke and pyrotechnics, and you can explore further on your own.

CIFilter Transitions

Core Image filters (Chapter 2) include transitions. You supply two images and a frame time between 0 and 1; the filter supplies the corresponding frame of a one-second animation transitioning from the first image to the second. For example, Figure 4-7 shows the frame at frame time .75 for a starburst transition from a solid red image to a photo of me. (You don’t see the photo of me, because this transition, by default, “explodes” the first image to white first, and then quickly fades to the second image.)

Midway through a starburst transition
Figure 4-7. Midway through a starburst transition

Animating a Core Image transition filter is up to you. Thus we need a way of rapidly calling the same method repeatedly; in that method, we’ll request and draw each frame of the transition. This could be a job for an NSTimer, but an even better way is to use a display link (CADisplayLink), a form of timer that’s highly efficient, especially when repeated drawing is involved, because it is linked directly to the refreshing of the display (hence the name). The display refresh rate is typically about one-sixtieth of a second; the actual value is given as the display link’s duration, and will undergo slight fluctuations. Like a timer, the display link calls a designated method of ours every time it fires. We can slow the rate of calls by an integral amount by setting the display link’s frameInterval; for example, a display link with a frameInterval of 2 will call us about every one-thirtieth of a second. We can learn the exact time when the display link last fired by querying its timestamp.

In this example, I’ll display the animation in a view’s layer. We start by initializing and storing ahead of time, in instance variables, everything we’ll need later to obtain an output image for a given frame of the transition: the CIFilter, the image’s extent, and the CIContext used for rendering. We also have instance variables _frame and _timestamp, which we initialize as well:

UIImage* moi = [UIImage imageNamed:@"moi"];
CIImage* moi2 = [[CIImage alloc] initWithCGImage:moi.CGImage];
self->_moiextent = moi2.extent;
CIFilter* col = [CIFilter filterWithName:@"CIConstantColorGenerator"];
CIColor* cicol = [[CIColor alloc] initWithColor:[UIColor redColor]];
[col setValue:cicol forKey:@"inputColor"];
CIImage* colorimage = [col valueForKey: @"outputImage"];
CIFilter* tran = [CIFilter filterWithName:@"CIFlashTransition"];
[tran setValue:colorimage forKey:@"inputImage"];
[tran setValue:moi2 forKey:@"inputTargetImage"];
CIVector* center =
    [CIVector vectorWithX:self->_moiextent.size.width/2.0
                        Y:self->_moiextent.size.height/2.0];
[tran setValue:center forKey:@"inputCenter"];
self->_con = [CIContext contextWithOptions:nil];
self->_tran = tran;
self->_timestamp = 0.0;

We create the display link, setting it to call into our nextFrame: method, and set it going by adding it to the run loop, which retains it:

CADisplayLink* link =
    [CADisplayLink displayLinkWithTarget:self
        selector:@selector(nextFrame:)];
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

Our nextFrame: method is called with the display link as parameter (sender). We store the initial timestamp in an instance variable, and use the difference between that and each successive timestamp value to calculate our desired frame. We ask the filter for the corresponding image and display it. When the frame value exceeds 1, the animation is over and we invalidate the display link (just like a repeating timer), which releases it from the run loop:

if (self->_timestamp < 0.01) { // pick up and store first timestamp
    self->_timestamp = sender.timestamp;
    self->_frame = 0.0;
} else { // calculate frame
    self->_frame = sender.timestamp - self->_timestamp;
}
sender.paused = YES; // defend against frame loss
// get frame image and show it
[_tran setValue:@(self->_frame) forKey:@"inputTime"];
CGImageRef moi = [self->_con createCGImage:_tran.outputImage
                                  fromRect:_moiextent];
[CATransaction setDisableActions:YES];
self.v.layer.contents = (__bridge id)moi;
CGImageRelease(moi);
// check for done, clean up
if (_frame > 1.0) {
    [sender invalidate];
}
sender.paused = NO;

I have surrounded the time-consuming calculation and drawing of the image with calls to the display link’s paused property, in case the calculation time exceeds the time between screen refreshes; perhaps this isn’t necessary, but it can’t hurt. Our animation occupies one second; changing that value is merely a matter of multiplying by a scale value when we set our _frame instance variable. If you experiment with this code, run on the device, as display links do not work well in the Simulator.

UIKit Dynamics

A suite of classes, UIKit dynamics, supplies a convenient API for animating views in a manner reminiscent of real-world physical behavior. For example, views can be subjected to gravity, collisions, and momentary forces, with effects that would otherwise be difficult to achieve.

UIKit dynamics should not be treated as a game engine. It is deliberately quite cartoony and simple, treating views as rectangular blocks and animating only their position (center) and rotation transform within a flat two-dimensional space. Nor is it intended for extended use. Like other ways of achieving animation, it is a way of momentarily emphasizing or clarifying functional transformations of your interface.

Implementing UIKit dynamics involves configuring a “stack” of three things:

A dynamic animator
A dynamic animator, a UIDynamicAnimator instance, is the ruler of the physics world you are creating. It has a reference view, which is the superview of the views to be animated, and which defines the coordinate system of its world. Retaining the animator is up to you; a strong property will do. It’s fine for an animator to sit empty until you need it; an animator whose world is empty (or at rest) is not running, and occupies no processor time.
A behavior
A UIDynamicBehavior is a rule describing how a view should behave. You’ll typically use a built-in subclass, such as UIGravityBehavior or UICollisionBehavior. You configure the behavior and add it to the animator; an animator has methods and properties for managing its behaviors, such as addBehavior:, behaviors, removeBehavior:, and removeAllBehaviors. A behavior’s configuration can be changed, and behaviors can be added to and removed from an animator, even while an animation is in progress.
An item

An item is any object that implements the UIDynamicItem protocol. A UIView is such an object! You add a UIView (one that’s a subview of your animator’s reference view) to a behavior (one that belongs to that animator) — and at that moment, the view comes under the influence of that behavior. If this behavior is one that causes motion, and if no other behaviors prevent, the view will now move (the animator is running).

Some behaviors can accept multiple items, and have methods and properties such as addItem:, items, and removeItem:. Others can have just one or two items and must be initialized with these from the outset.

That’s sufficient to get started, so let’s try it! I’ll start by creating my animator and storing it in a property:

self.anim = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

Now I’ll cause an existing subview of self.view (a UIImageView, self.iv) to drop off the screen, under the influence of gravity. I create a UIGravityBehavior, add it to the animator, and add self.iv to it:

UIGravityBehavior* grav = [UIGravityBehavior new];
[self.anim addBehavior:grav];
[grav addItem:self.iv];

As a result, self.iv comes under the influence of gravity and is now animated downward off the screen. (A UIGravityBehavior object has properties configuring the strength and direction of gravity, but I’ve left them here at their defaults.)

An immediate concern is that our view falls forever. This is a waste of memory and processing power. If we no longer need the view after it has left the screen, we should take it out of the influence of UIKit dynamics by removing it from any behaviors to which it belongs (and we can also remove it from its superview). One way to do this is by removing from the animator any behaviors that are no longer needed. In our simple example, where the animator’s entire world contains just this one item, it will be sufficient to call removeAllBehaviors.

But how will we know when the view is off the screen? A UIDynamicBehavior can have an action block, which is called repeatedly as the animator drives the animation. I’ll use this block to check whether self.iv is still within the bounds of the reference view, by calling the animator’s itemsInRect: method:

grav.action = ^{
    NSArray* items = [self.anim itemsInRect:self.view.bounds];
    if (NSNotFound == [items indexOfObject:self.iv]) {
        [self.anim removeAllBehaviors];
        [self.iv removeFromSuperview];
    }
};

Warning

If a dynamic behavior’s action block refers to the dynamic behavior itself, there’s a danger of a retain cycle, because the behavior retains the block which refers to the behavior. Express yourself in some other way (perhaps attaching the block to some other behavior), or use the weak–strong dance to break the retain cycle in the block.

Let’s add some further behaviors. If falling straight down is too boring, we can add a UIPushBehavior to create a slight rightward impulse to be applied to the view as it begins to fall:

UIPushBehavior* push =
    [[UIPushBehavior alloc] initWithItems:@[self.iv]
        mode:UIPushBehaviorModeInstantaneous];
push.pushDirection = CGVectorMake(2, 0);
[self.anim addBehavior:push];

The view now falls in a parabola to the right.

Next, let’s add a UICollisionBehavior to make our view strike the “floor” of the screen:

UICollisionBehavior* coll = [UICollisionBehavior new];
coll.collisionMode = UICollisionBehaviorModeBoundaries;
[coll addBoundaryWithIdentifier:@"floor"
    fromPoint:CGPointMake(0,self.view.bounds.size.height)
    toPoint:CGPointMake(self.view.bounds.size.width,
                        self.view.bounds.size.height)];
[self.anim addBehavior:coll];
[coll addItem:self.iv];

The view now falls in a parabola onto the floor of the screen, bounces a tiny bit, and comes to rest. It would be nice if the view bounced a bit more. Characteristics internal to a dynamic item’s physics, such as bounciness (elasticity), are configured by assigning it to a UIDynamicItemBehavior:

UIDynamicItemBehavior* bounce = [UIDynamicItemBehavior new];
bounce.elasticity = 0.4;
[self.anim addBehavior:bounce];
[bounce addItem:self.iv];

Our view now bounces higher; nevertheless, when it hits the floor, it stops moving to the right, so it ends up at rest on the floor. I’d prefer that, after it bounces, it should start spinning to the right, so that it eventually leaves the screen. A UICollisionBehavior has a delegate to which it sends messages when a collision occurs. I’ll make self the collision behavior’s delegate, and when the delegate message arrives, I’ll add rotational velocity to the existing dynamic item behavior bounce, so that our view starts spinning clockwise:

-(void)collisionBehavior:(UICollisionBehavior *)behavior
        beganContactForItem:(id<UIDynamicItem>)item
        withBoundaryIdentifier:(id<NSCopying>)identifier
        atPoint:(CGPoint)p {
    for (UIDynamicBehavior* b in self.anim.behaviors) {
        if ([b isKindOfClass: [UIDynamicItemBehavior class]]) {
            UIDynamicItemBehavior* bounce = (UIDynamicItemBehavior*) b;
            CGFloat v = [bounce angularVelocityForItem:self.iv];
            if (v <= 0.1) // do this just once
                [bounce addAngularVelocity:30 forItem:self.iv];
            break;
        }
    }
}

The view now falls in a parabola to the right, strikes the floor, spins clockwise, and bounces off the floor and out the right side of the screen!

We have now developed a complex behavior by a combination of several built-in UIDynamicBehavior subclass instances. For neatness, clarity, maintainability, and reusability, it might make sense to express that combination as a single custom UIDynamicBehavior subclass. Let’s call it MyDropBounceAndRollBehavior. Now we can apply this behavior to our view, self.iv, very simply:

[self.anim addBehavior:
    [[MyDropBounceAndRollBehavior alloc] initWithView:self.iv]];

All the work is now done by the MyDropBounceAndRollBehavior instance. This instance has received a reference to the view to be animated, and it may reasonably assume that its superview is the dynamic animator’s reference view (if not, we could provide the reference view as another parameter to the initializer). A UIDynamicBehavior receives a reference to its dynamic animator just before being added to it, by implementing willMoveToAnimator:, and can refer to it subsequently as self.dynamicAnimator. To incorporate actual behaviors into itself, our custom UIDynamicBehavior subclass creates and configures them, and calls addChildBehavior:; it can refer to the array of its child behaviors as self.childBehaviors. When our custom behavior is added to or removed from the dynamic animator, the effect is the same as if its child behaviors themselves were added or removed.

Here is our UIDynamicAnimator subclass. Assume that the initializer has stored the incoming view to be animated in a property v. Observe that, as I warned earlier, we must take care in the action block not to cause a retain cycle:

-(void)willMoveToAnimator:(UIDynamicAnimator *)anim {
    if (!anim)
        return;
    UIView* sup = self.v.superview;
    // the gravity child
    UIGravityBehavior* grav = [UIGravityBehavior new];
    __weak MyDropBounceAndRollBehavior* wself = self;
    grav.action = ^{
        MyDropBounceAndRollBehavior* sself = wself;
        if (sself) {
            NSArray* items = [anim itemsInRect:sup.bounds];
            if (NSNotFound == [items indexOfObject:sself.v]) {
                [anim removeBehavior:sself];
                [sself.v removeFromSuperview];
            }
        }
    };
    [self addChildBehavior:grav];
    [grav addItem:self.v];
    // the push child
    UIPushBehavior* push =
        [[UIPushBehavior alloc] initWithItems:@[self.v]
            mode:UIPushBehaviorModeInstantaneous];
    push.pushDirection = CGVectorMake(2, 0);
    [self addChildBehavior:push];
    // the collision child
    UICollisionBehavior* coll = [UICollisionBehavior new];
    coll.collisionMode = UICollisionBehaviorModeBoundaries;
    coll.collisionDelegate = self;
    [coll addBoundaryWithIdentifier:@"floor"
                          fromPoint:CGPointMake(0,sup.bounds.size.height)
                            toPoint:CGPointMake(sup.bounds.size.width,
                                                sup.bounds.size.height)];
    [self addChildBehavior:coll];
    [coll addItem:self.v];
    // the bounce child
    UIDynamicItemBehavior* bounce = [UIDynamicItemBehavior new];
    bounce.elasticity = 0.4;
    [self addChildBehavior:bounce];
    [bounce addItem:self.v];
}
-(void)collisionBehavior:(UICollisionBehavior *)behavior
        beganContactForItem:(id<UIDynamicItem>)item
        withBoundaryIdentifier:(id<NSCopying>)identifier
        atPoint:(CGPoint)p {
    for (UIDynamicBehavior* b in self.childBehaviors) {
        if ([b isKindOfClass: [UIDynamicItemBehavior class]]) {
            UIDynamicItemBehavior* bounce = (UIDynamicItemBehavior*) b;
            CGFloat v = [bounce angularVelocityForItem:item];
            if (v <= 0.1) {
                [bounce addAngularVelocity:30 forItem:item];
            }
            break;
        }
    }
}

Here are some further UIDynamicAnimator methods and properties:

delegate
The delegate (UIDynamicAnimatorDelegate) is sent messages dynamicAnimatorDidPause: and dynamicAnimatorWillResume:. The animator is paused when it has nothing to do: it has no dynamic items, or all its dynamic items are at rest.
running
If YES, the animator is not paused; some dynamic item is being animated.
elapsedTime
The total time during which this animator has been running since it first started running. The elapsedTime does not increase while the animator is paused, nor is it reset. You might use this in a delegate method or action method to decide that the animation is over.
updateItemUsingCurrentState:
Once a dynamic item has come under the influence of the animator, the animator is responsible for positioning that dynamic item. If your code subsequently manually changes the dynamic item’s position or other relevant attributes, call this method so that the animator can take account of those changes.

Here is some more about the various built-in UIDynamicBehavior subclasses:

UIGravityBehavior
Imposes an acceleration on its dynamic items. By default, this acceleration is downward with a magnitude of 1 (arbitrarily defined as 1000 points per second per second).
UIPushBehavior

Applies a force either instantaneously or continuously (mode), the latter constituting an acceleration. How this force affects an object depends in part upon the object’s “mass”, which is based on its size combined with its density (the latter can be set through a UIDynamicItemBehavior); thus, by default, a smaller view is easier to push. The effect of a push behavior can be toggled with the active property; an instantaneous push is repeated each time the active property is set to YES.

In addition to a direction and a magnitude, a push may be given an offset from the center of an item. This will apply an additional angular acceleration. Thus, I could have started self.iv spinning clockwise by means of its initial push, like this:

[push setTargetOffsetFromCenter:UIOffsetMake(0, -200) forItem:self.iv];
UICollisionBehavior

Watches for collisions either amongst items belonging to this same behavior or between an item and a boundary (mode). One collision behavior can have many boundaries. A boundary may be described as a line between two points or as a UIBezierPath, or you can turn the reference view’s bounds into boundaries (setTranslatesReferenceBoundsIntoBoundaryWithInsets:). Boundaries that you create can have an identifier. The collisionDelegate (UICollisionBehaviorDelegate) is called when a collision begins and again when it ends.

How a given collision affects the item(s) involved depends on the physical characteristics of the item(s), which may be configured through a UIDynamicItemBehavior.

UISnapBehavior
Causes one item to snap to one point as if pulled by a spring. Its damping describes how much the item should oscillate as its settles into that point. This is a very simple behavior: the snap occurs once, immediately (when the behavior is added to the animator), and there’s no notification when it’s over.
UIAttachmentBehavior

Attaches an item by a bar or a spring to another item (initWithItem:attachedToItem:) or to a point in the reference view (initWithItem:attachedToAnchor:). The attachment point is, by default, the item’s center; to change that, initialize with initWithItem:offsetFromCenter:attachedToItem:offsetFromCenter: or initWithItem:offsetFromCenter:attachedToAnchor:.

The physics of the attaching medium is governed by the behavior’s length, frequency, and damping. They are set for you when you initialize the behavior, but you can modify them, and the anchorPoint (if attachment is to an anchor), over the behavior’s lifetime.

As the other item or the anchorPoint moves, this item moves with it, in accordance with the physics of the attaching medium. An anchorPoint is particularly useful for implementing a draggable view within an animator world, as I’ll demonstrate in the next chapter.

UIDynamicItemBehavior
Endows its items with internal physical characteristics such as density (changes the impulse-resisting mass in relation to size), elasticity (bounce on collision), friction, and resistance (tendency to come to rest unless forces are actively applied), as well as injecting linear velocity or angular velocity.

Motion Effects

New in iOS 7, a view can respond in real time to the way the user tilts the device. Typically, the view’s response will be to shift its position slightly. This is used, for example, in various parts of the interface, to give a sense of the interface’s being layered. When a UIAlertView is present, if the user tilts the device, the UIAlertView shifts its position; the effect is subtle, but sufficient to suggest subconsciously that the UIAlertView is floating slightly in front of everything else on the screen.

Your own views can behave in the same way. A view will respond to shifts in the position of the device if it has one or more motion effects (UIMotionEffect). Motion effects are added to a view with addMotionEffect:, listed with motionEffects, and removed with removeMotionEffect:.

The UIMotionEffect class is abstract: its job is to be subclassed. The chief subclass provided is UIInterpolatingMotionEffect. Every UIInterpolatingMotionEffect has a single key path, which uses key–value coding to specify the property it affects. It also has a type, specifying which axis of the device’s tilting (horizontal tilt or vertical tilt) is to affect this property. Finally, it has a maximum and minimum relative value, the furthest distance that the affected property of the view is to be permitted to wander from its actual value as the user tilts the device. Related motion effects should be combined into a UIMotionEffectGroup (a UIMotionEffect subclass), and the group added to the view.

So, for example:

UIInterpolatingMotionEffect* m1 =
    [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x"
    type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
m1.maximumRelativeValue = @10.0;
m1.minimumRelativeValue = @-10.0;
UIInterpolatingMotionEffect* m2 =
    [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y"
    type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
m2.maximumRelativeValue = @10.0;
m2.minimumRelativeValue = @-10.0;
UIMotionEffectGroup* g = [UIMotionEffectGroup new];
g.motionEffects = @[m1,m2];
[self.mars addMotionEffect:g];

You can write your own UIMotionEffect subclass by implementing a single method, keyPathsAndRelativeValuesForViewerOffset:, but this will rarely be necessary.

Note

The user can turn off motion effects in the Settings app (under General → Accessibility → Reduce Motion).

Animation and Autolayout

The interplay between animation and autolayout can be tricky. As part of an animation, you may be changing a view’s frame (or bounds, or center). You’re really not supposed to do that when you’re using autolayout. As a result, an animation may not work correctly. Or, it may appear to work perfectly, because no layout has happened; however, it is entirely possible that layout will happen, and that it will be accompanied by undesirable effects.

As I explained in Chapter 1, when layout takes place under autolayout, what matters are a view’s constraints. If the constraints affecting a view don’t resolve to the size and position that the view has at the moment of layout, the view will jump as the constraints are obeyed. This is almost certainly not what you want.

To persuade yourself that this can be a problem, just animate a view’s position and then ask for immediate layout by calling layoutIfNeeded, like this:

CGPoint p = self.v.center;
p.x += 100;
[UIView animateWithDuration:1 animations:^{
    self.v.center = p;
} completion:^(BOOL b){
    [self.v layoutIfNeeded]; // this is what will happen at layout time
}];

If we’re using autolayout, the view slides to the right and then jumps back to the left. This is bad. It’s up to us to keep the constraints synchronized with the reality, so that when layout comes along in the natural course of things, our views don’t jump into undesirable states.

One option is to revise the violated constraints to match the new reality. If we’ve planned far ahead, we may have armed ourselves in advance with a reference to those constraints; in that case, our code can now remove and replace them — or, if the only thing that needs changing is the constant value of a constraint, we can change that value in place (recall that the constant is the only writable property of an existing constraint). Otherwise, discovering what constraints are now violated, and getting a reference to them, is not at all easy.

An alternative approach, in the case where the only thing that needs changing is a constraint’s constant, is this: instead of animating the view’s position and then compensating by changing the constant value of the constraint that positions it, animate the change in the constant value in the first place. To do so, we set the constraint’s constant to its new value, and animate the act of layout. Again, this assumes that we have a reference to the constraint in question.

For example, if we are animating a view 100 points rightward, and if we have a reference to the constraint whose constant positions that view horizontally, we would say this:

// con is the constraint
con.constant += 100;
[UIView animateWithDuration:1 animations:^{
    [self.v layoutIfNeeded];
}];

Another issue has to do with view transforms. As I said at the end of Chapter 1, applying a view transform triggers layout immediately, and constraints then take a hand in positioning the view. Thus an animation involving a view transform will likely misbehave under autolayout.

For example, you would expect a simple autoreversing animation that waggles a view, or scales it up and back down, to work under autolayout (after all, we’re not ultimately changing anything’s frame). But, alas, that’s not true. Even this simple “throb” animation can break under autolayout — instead of simply throbbing, the view may also jump momentarily to a different position:

[UIView animateWithDuration:0.3 delay:0
        options:UIViewAnimationOptionAutoreverse animations:^{
    self.v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
    self.v.transform = CGAffineTransformIdentity;
}];

One solution in this case is to use Core Animation instead; this works because applying a layer transform, unlike a view transform, does not trigger layout:

CABasicAnimation* ba =
    [CABasicAnimation animationWithKeyPath:@"transform"];
ba.autoreverses = YES;
ba.duration = 0.3;
ba.toValue =
    [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
[self.v.layer addAnimation:ba forKey:nil];

Another possibility is to use a snapshot of the original view (Chapter 1). Add the snapshot temporarily to the interface — without using autolayout, and perhaps hiding the original view — and animate the snapshot:

UIView* snap = [self.v snapshotViewAfterScreenUpdates:YES];
snap.frame = self.v.frame;
[self.v.superview addSubview:snap];
self.v.hidden = YES;
[UIView animateWithDuration:0.3 delay:0
         options:UIViewAnimationOptionAutoreverse animations:^{
    snap.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
    snap.transform = CGAffineTransformIdentity;
    self.v.hidden = NO;
    [snap removeFromSuperview];
}];

However, if the nature of the animation is such that the real view ultimately needs to be shifted to a new permanent position, then its constraints will still have to be revised.

Another useful trick is to take advantage of the fact that the “animation movie” masks the reality. In this example from one of my apps, I apparently shrink a view (english) down to nothingness:

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"opacity"];
self.english.layer.opacity = 0;
ba.duration = 0.2;
[self.english.layer addAnimation:ba forKey:nil];
CABasicAnimation* ba2 = [CABasicAnimation animationWithKeyPath:@"bounds"];
ba2.duration = 0.2;
ba2.toValue = [NSValue valueWithCGRect:self.english.layer.bounds];
[self.english.layer addAnimation:ba2 forKey:nil];

This doesn’t break under autolayout, because I never did anything to violate any existing constraints: I never changed the view’s bounds! I did, however, make it invisible, changing its opacity to 0. The “animation movie”, on the other hand, portrays the view as shrinking to nothingness, and also as fading away; and by the time the “animation movie” is ripped away, the view is invisible, so the user doesn’t see that it’s actually still at its full size.

Yet another possibility, as I suggested at the end of Chapter 1, is to combine an invisible “host view” that is positioned in relation to the surrounding interface by autolayout with a subview that is not positioned in relation to its superview by autolayout — and can thus be animated however you like without violating any constraints.

Warning

The need for such elaborate tactics is most unfortunate. Autolayout was introduced into iOS 6 with a seeming disregard for its fundamental incompatibility with animation. That incompatibility is a serious flaw in iOS, and Apple, far from acknowledging and grappling with it, has studiously glossed over it.

Get Programming iOS 7, 4th Edition now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.