O'Reilly logo

Programming iOS 8 by Matt Neuburg

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

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. This book doesn’t discuss Sprite Kit, but an understanding of the concepts in this chapter will prepare you very well for Sprite Kit.)

Tip

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: it was to allow 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.”

There isn’t really an “animation movie” in front of the screen — but it’s a good analogy, and the effect is much the same. In reality, it is not a layer itself that is portrayed on the screen; it’s a derived layer called the presentation layer. Thus, when you animate the change of a view’s position or a layer’s position from position 1 to position 2, its nominal position changes immediately; meanwhile, the presentation layer’s position remains unchanged until the redraw moment, and then changes over time, and because that’s what’s actually drawn on the screen, that’s what the user sees.

(A layer’s presentation layer can be accessed through its presentationLayer method — and the layer itself may be accessed through the presentation layer’s modelLayer method. I’ll give examples later in this chapter and in the next chapter of situations where accessing the presentation layer is a useful thing to do.)

Tip

The existence of the presentation layer can be confusing, because it means that every animated layer is copied. You may need to be aware of this fact when designing a custom CALayer subclass. For example, you might use logging or breakpoints to test when certain events take place in an instance of your custom CALayer; you may think there is only such instance, but if this layer is animated, there are two such instances, and your console messages or pauses may take place in the presentation layer rather than in your “real” layer.

The “animation movie” analogy is an apt one, because, like a movie (especially an old-fashioned animated cartoon), there are “frames.” An animating layer does not change smoothly and continuously; it changes in small, individual increments that give the illusion of smooth, continuous change. This illusion works because the device itself undergoes a periodic, rapid, more or less regular screen refresh, and the incremental changes are made to fall between these refreshes. Apple calls the system component responsible for this the animation server.

Animation (meaning the animation server) operates 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.

Note

New in iOS 8, conflicting view animations may automatically be combined additively, rather than the second one superseding the first. I’ll talk more about that later in this chapter.

Outside forces can interrupt your animations as well. The user might click the Home button to send your app to the background, or an incoming phone call might arrive 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 resume an animation in the middle, where it left off when it was interrupted, 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:

let mars = UIImage(named: "Mars")!
UIGraphicsBeginImageContextWithOptions(mars.size, false, 0)
let empty = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let arr = [mars, empty, mars, empty, mars]
let iv = UIImageView(image:empty)
iv.frame.origin = CGPointMake(100,100)
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.

UIImage supplies a form of animation parallel to that of UIImageView: an image can itself be an animated image. Just as with UIImageView, this really means that you’ve prepared multiple images that form a sequence serving as the “frames” of a simple cartoon. You can create 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 init(named:), 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:

var arr = [UIImage]()
let w : CGFloat = 18
for i in 0 ..< 6 {
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(w,w), false, 0)
    let con = UIGraphicsGetCurrentContext()
    CGContextSetFillColorWithColor(con, UIColor.redColor().CGColor)
    let ii = CGFloat(i)
    CGContextAddEllipseInRect(con, CGRectMake(0+ii,0+ii,w-ii*2,w-ii*2))
    CGContextFillPath(con)
    let im = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    arr += [im]
}
let im = UIImage.animatedImageWithImages(arr, duration:0.5)
b.setImage(im, forState:.Normal) // b is a button in the interface

View Animation

All 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.)

The syntax for animating a UIView involves calling a UIView class method and expressing the desired animation in a closure. Such a closure corresponds to an Objective-C block, so the documentation refers to this as block-based animation, and I will use phrases such as “animation block” or “animations: block” even though “block” is not the official Swift term for such a construct.

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()
})

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()
    self.v.center.y += 100
})

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:

let v2 = // ... create and configure new view here ...
v2.alpha = 0
self.v.superview!.addSubview(v2)
UIView.animateWithDuration(0.4, animations: {
    self.v.alpha = 0
    v2.alpha = 1
    }, completion: {
        _ in
        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 the preceding 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 the animations: block.

Sometimes, though, that’s not so easy; perhaps, within the animations: block, we must call a method that might perform animatable changes. 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 {
        self.v.center.y += 100
    }
})

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. This code, for example, is essentially incoherent:

UIView.animateWithDuration(2, animations: {
    self.v.center.y = 100
})
self.v.center.y = 300

What actually happens is that the view jumps to a center y position of 100, even though that setting is inside the animation block, and then animates to a center y position of 300, even though that setting is outside the animation block. What has happened, in effect, is that the second setting of self.v.center.y has cancelled the first animation and replaced it with another. I could try to explain why this is, but I’m not even sure I understand it completely; what I do know is that you should try not to do that sort of thing. 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.

On the other hand, this code jumps and then animates on iOS 7 and before, but on iOS 8 it does a smooth single animation from its original position to a center y position of 300:

UIView.animateWithDuration(2, animations: {
    self.v.center.y = 100
    self.v.center.y = 300
})

This is an important change in iOS 8 animation behavior, and I do know the reason for it (because Apple explains it in a WWDC 2014 video): it’s because basic positional view animations are additive by default in iOS 8. This means, in effect, that although there are two animations of the same view attribute in that code, the second one does not cancel or conflict with the first, as it did in iOS 7 and before; instead, both animations are allowed to stand, and the second one is run simultaneously with the first, and is blended with it.

Note

In that example, it’s particularly obvious what such blending entails. Sometimes, however, this new iOS 8 blending of animations can give results that may initially surprise you. If your existing iOS 7 animation code results in new behavior when your app is compiled for iOS 8, additive animations may be the reason.

View Animation Options

The UIView class methods animateWithDuration: and animateWithDuration:completion: are both reduced forms. The full form of this method, which you should use whenever you need the maximum in flexibility and power, is animateWithDuration:delay:options:animations:completion:. 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. A delay is not the same as applying the animation using delayed performance; the animation is applied immediately, but when it starts running it spins its wheels, with no visible change, until the delay time has elapsed.
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 true, 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 (UIViewAnimationOptions) that 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:

  • .CurveEaseInOut (the default)
  • .CurveEaseIn
  • .CurveEaseOut
  • .CurveLinear (constant speed throughout)
.Repeat
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.
.Autoreverse
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 .Autoreverse, 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:

let opts = UIViewAnimationOptions.Autoreverse
UIView.animateWithDuration(1, delay: 0, options: opts, animations: {
    self.v.center.x += 100
    }, 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 for the jump 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:

let opts = UIViewAnimationOptions.Autoreverse
let xorig = self.v.center.x
UIView.animateWithDuration(1, delay: 0, options: opts, animations: {
    self.v.center.x += 100
    }, completion: {
        _ in
        self.v.center.x = xorig
})

Working around the inability to specify a finite number of repetitions is not easy. Let’s say you want to repeat the above animation exactly three times. A simple counting loop won’t work, because animations are asynchronous and time-consuming. One clear solution is to append a tail-recursion to the completion: handler:

func animate(count:Int) {
    let opts = UIViewAnimationOptions.Autoreverse
    let xorig = self.v.center.x
    UIView.animateWithDuration(1, delay: 0, options: opts, animations: {
        self.v.center.x += 100
        }, completion: {
            _ in
            self.v.center.x = xorig
            if count > 1 {
                self.animate(count-1)
            }
    })
}

If we call the animate: method with an argument of 3, 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. (That example, of course, suffers from lack of generality; for a general utility function that can do a finite count repetition of any animation, see Appendix B.)

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

.BeginFromCurrentState
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), if that is what would normally happen, this animation will use the presentation layer to decide where to start, and, if possible, will “blend” its animation with the previous animation.
.OverrideInheritedDuration
Prevents inheriting the duration from a surrounding or in-flight animation (the default is to inherit it).
.OverrideInheritedCurve
Prevents inheriting the animation curve from a surrounding or in-flight animation (the default is to inherit it).

To illustrate .BeginFromCurrentState is not as easy as it was in iOS 7 and before, because simple view animations, as I’ve already said, are additive by default in iOS 8. Consider the following:

UIView.animateWithDuration(1, animations: {
    self.v.center.x += 100
})
UIView.animateWithDuration(1, delay: 0, options: nil,
    animations: {
    self.v.center.y += 100
}, completion: nil)

In iOS 7, that code would cause the view to jump 100 points rightward and then animate 100 points downward. That’s because both animations have to do with the position of the view, and they conflict; the second animation therefore caused the first animation to be thrown away. Setting options: to .BeginFromCurrentState, on the other hand, caused the two animations to combine: the view animates diagonally down and to the right.

In iOS 8, however, additive animation is the default, so the view animates diagonally down and to the right even if options: is nil.

Thus, .BeginFromCurrentState is unlikely to be useful in iOS 8 — though it does no harm to use it. Observe, however, that the effect of additive animations in iOS 8 is not identical to the effect of .BeginFromCurrentState in iOS 7. To see the difference, experiment with code such as this:

UIView.animateWithDuration(2, animations: {
    self.v.center.x += 100
})
delay(1) {
    let opts = UIViewAnimationOptions.BeginFromCurrentState
    UIView.animateWithDuration(1, delay: 0, options: opts,
        animations: {
            self.v.center.y += 100
        }, completion: nil)
}

The second animation launches under delayed performance halfway through the first animation. In iOS 7, this stops the first animation dead in its tracks; the animation does resume from that stopping point, but the view turns a sharp corner and makes a beeline for its final position (down and to the right). In iOS 8, on the other hand, the view never stops moving, because the first animation is never cancelled; when the second animation starts, the view turns the corner gently, with some residual horizontal motion from the first animation.

Canceling a View Animation

Once a view animation is in-flight, how can you cancel it? This has always been a tricky problem, and the change in iOS 8 to additive view animations actually makes it even trickier.

The simple answer, which is not tricky at all, is to reach down to the CALayer level and call removeAllAnimations. (If the layer has more than one animation and you only want to cancel one of them, you can call removeAnimationForKey:; I’ll talk later in this chapter about how to distinguish layer animations by key.) This has the advantage of simplicity, but the disadvantage that it simply stops the animation dead: the “animation movie” is whipped away instantly, leaving the view at its final position.

To illustrate, I’ll start with a simple unidirectional positional animation, with a long duration so that we can interrupt it in mid-flight (by tapping a button, for example). To facilitate the explanation, I’ll conserve both the view’s original position and its final position in properties:

self.pOrig = self.v.center
self.pFinal = self.v.center
self.pFinal.x += 100
UIView.animateWithDuration(4, animations: {
    self.v.center = self.pFinal
})

We can certainly stop that animation abruptly by mindlessly removing all animations from the layer; this is effectively the same as what the system does automatically when the app goes into the background:

self.v.layer.removeAllAnimations()

That code, obviously, jumps the view to its final position — because that’s where it really is, and that fact is revealed when the “animation movie” is removed. Now let’s try to devise a more subtle form of cancellation: the view should hurry to its final position. In iOS 7 and before, the way to do this was to order another animation that brings the animated view rapidly 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 must generate a conflict between the two animations. The solution was to order a very slightly different, conflicting animation and use its completion: handler to assign the view property its true final value:

let opts = UIViewAnimationOptions.BeginFromCurrentState
UIView.animateWithDuration(0.1, delay:0.1, options:opts,
    animations: {
        var p = self.pFinal!
        p.x += 1
        self.v.center = p
    }, completion: {
        _ in
        self.v.center = self.pFinal
})

That doesn’t work in iOS 8, however, because the two animations don’t conflict; they are additive. The second animation thus doesn’t remove the first animation, which is what we’re trying to accomplish. Therefore we must remove the first animation manually. We already know how to do that: call removeAllAnimations. But we also know that if we do that, the view will jump to its final position; we want it to remain, for the moment, at its current position — meaning the animation’s current position. That position is where the presentation layer currently is. Therefore we reposition the view at the location of its presentation layer, and then remove the animation, and then perform the final “hurry home” animation:

self.v.layer.position = self.v.layer.presentationLayer().position
self.v.layer.removeAllAnimations()
UIView.animateWithDuration(0.1, animations: {
    self.v.center = self.pFinal
})

One nice thing about our solution is that if we decide that cancellation means returning the view to its original position, we have only to set the view’s center to self.pOrig instead of self.pFinal.

Now let’s suppose that the animation we want to cancel is an infinitely repeating autoreversing animation:

self.pOrig = self.v.center
let opts : UIViewAnimationOptions = .Autoreverse | .Repeat
UIView.animateWithDuration(1, delay: 0, options: opts,
    animations: {
        self.v.center.x += 100
    }, completion: nil)

In that case, the iOS 7 technique of imposing a conflicting animation does work in iOS 8, because the new animation is not additive with the first one. The reason is that, as I’ve said already, only simple view animations are additive in iOS 8; I have not elaborated on what “simple” means, but one thing it means is “not repeating.” So here’s how to cancel that animation by returning it rapidly to its original position:

UIView.animateWithDuration(0.1, delay:0,
    options:.BeginFromCurrentState,
    animations: {
        self.v.center = self.pOrig
    }, completion:nil)

The .BeginFromCurrentState option is necessary to prevent the view from jumping momentarily to the “final” position, 100 points to the right, to which we set it to initiate the repeating animation.

(If you object that storage of the view’s original or final position as a view controller property is not a very encapsulated solution, then consider storing it instead in the view’s layer using key–value coding. The implementation is left as an exercise for the reader. Hint: a CGPoint will need to be wrapped in an NSValue.)

Custom Animatable View Properties

Even though the view attributes that are animatable through block-based view animation are officially limited to its alpha, backgroundColor, bounds, center, frame, and transform, you can define your own custom view properties that respond by animating when they themselves are changed in an animation block.

Here’s an example of what I mean. Imagine a UIView subclass, MyView, which has a Bool swing property. All this does is reposition the view: when swing is set to true, the view’s center x value is increased by 100; when swing is set to false, the view’s center x value is decreased by 100. A view’s center is animatable, so we can make it be the case that when a MyView’s swing is set in an animation block, the position change is animated.

The trick is a simple one (though I personally had never thought of it until an Apple WWDC 2014 video suggested it): implement MyView’s swing setter with a zero-duration animation block. This basically means that there is no animation by default, but if we happen to be inside an animation block already when the swing property is set, the setter’s animation block inherits the duration of the surrounding animation block — because such inheritance is, as I mentioned earlier, the default:

class MyView : UIView {
    var swing : Bool = false {
        didSet {
            var p = self.center
            p.x = self.swing ? p.x + 100 : p.x - 100
            UIView.animateWithDuration(0, animations: {
                self.center = p
                })
        }
    }
}

The result is exactly as I’ve already said. If, in code elsewhere, we change a MyView’s swing directly, the view jumps to its new position. But now suppose we change it in an animation block:

UIView.animateWithDuration(1, animations: {
    self.v.swing = !self.v.swing // "animatable" Bool property
})

In that case, the change in position is animated, with the specified duration of 1.

Springing View Animation

A springing view animation is an animation curve with a very fast ease-in and a very slow ease-out; the animation can even oscillate for a while around its final value, 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: 20,
    options: nil,
    animations: {
        self.v.center.y += 100
    }, 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 gives the view an initial “kick,” speeding up the initial ease-in and increasing 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. You can have a lot of waggly fun with smaller damping values and larger initial spring velocity values. Conversely, a small initial spring value (about 10 or less) and a high damping value (1.0 or close to it) gives a normal animation that wouldn’t particularly remind anyone of a spring, but that does have a pleasingly rapid beginning and slow ending; many of Apple’s own system animations are actually spring animations of that type (consider, for example, the way folders open in the springboard).

Keyframe View Animation

A view animation can be ordered as a set of keyframes. This means that, instead of a simple beginning and end point, you specify multiple 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. (Giving the start time and duration in seconds is a common beginner mistake.)

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

var p = self.v.center
let dur = 0.25
var start = 0.0
let dx : CGFloat = 100
let dy : CGFloat = 50
var dir : CGFloat = 1
UIView.animateKeyframesWithDuration(4,
    delay: 0, options: nil,
    animations: {
        UIView.addKeyframeWithRelativeStartTime(start,
            relativeDuration: dur,
            animations: {
                p.x += dx*dir; p.y += dy
                self.v.center = p
            })
        start += dur; dir *= -1
        UIView.addKeyframeWithRelativeStartTime(start,
            relativeDuration: dur,
            animations: {
                p.x += dx*dir; p.y += dy
                self.v.center = p
            })
        start += dur; dir *= -1
        UIView.addKeyframeWithRelativeStartTime(start,
            relativeDuration: dur,
            animations: {
                p.x += dx*dir; p.y += dy
                self.v.center = p
            })
        start += dur; dir *= -1
        UIView.addKeyframeWithRelativeStartTime(start,
            relativeDuration: dur,
            animations: {
                p.x += dx*dir; p.y += dy
                self.v.center = p
            })
    }, completion: nil)

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 (as soon as the previous one ends). In each keyframe, the view’s center x value increases or decreases by 100, alternately, while its center y value keeps increasing by 50.

The keyframe values are points in space and time; the actual animation interpolates between them. How this interpolation is done depends upon the options:, which are UIKeyframeAnimationOptions values whose names start with “CalculationMode.” The default is .CalculationModeLinear. 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 setting is .CalculationModeCubic, 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.

Because my keyframes are perfectly even, I could achieve the same effects by using .CalculationModePaced (same effect as .CalculationModeLinear) and .CalculationModeCubicPaced (same effect as .CalculationModeCubic). The 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, .CalculationModeDiscrete 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: nil,
    animations: {
        self.v.alpha = 0
        // ...

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

It is also legal and meaningful to supply an animation curve as part of the options: argument. Unfortunately, the documentation fails to make this clear; and Swift’s obsessive-compulsive attitude towards data types resists folding a UIViewAnimationOptions animation curve directly into a value typed as a UIViewKeyframeAnimationOptions. Yet if you don’t do it, the default is .CurveEaseInOut, which may not be what you want. Here’s how to combine .CalculationModeLinear with .CurveLinear:

let opt1 : UIViewKeyframeAnimationOptions = .CalculationModeLinear
let opt2 : UIViewAnimationOptions = .CurveLinear
let opts = opt1 | UIViewKeyframeAnimationOptions(opt2.rawValue)

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 UIView class methods:

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

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

  • .TransitionFlipFromLeft, .TransitionFlipFromRight
  • .TransitionCurlUp, .TransitionCurlDown
  • .TransitionFlipFromBottom, .TransitionFlipFromTop
  • .TransitionCrossDissolve

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:

let opts : UIViewAnimationOptions = .TransitionFlipFromLeft
UIView.transitionWithView(self.iv, duration: 0.8, options: opts,
    animations: {
        self.iv.image = UIImage(named:"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 as a change in a view’s center.

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

class MyView : UIView {
    var reverse = false
    override func drawRect(rect: CGRect)  {
        let f = self.bounds.rectByInsetting(dx: 10, dy: 10)
        let 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:

let opts : UIViewAnimationOptions = .TransitionFlipFromLeft
self.v.reverse = !self.v.reverse
UIView.transitionWithView(self.v, duration: 1, options: opts,
    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 — that is, if you want to display a subview of the transitioning view being animated as it assumes its final state — use .AllowAnimatedContent in the options bitmask.

transitionFromView:toView:... 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 .ShowHideTransitionViews 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 .ShowHideTransitionViews is one of the options, then both subviews are in the view hierarchy when we start; the hidden of the first is false, the hidden of the second is true, and the transition reverses these values.

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

let lab2 = UILabel(frame:self.lab.frame)
lab2.text = self.lab.text == "Hello" ? "Howdy" : "Hello"
lab2.sizeToFit()
UIView.transitionFromView(self.lab, toView: lab2,
    duration: 0.8, options: .TransitionFlipFromLeft,
    completion: {
        _ in
        self.lab = lab2
    })

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:

// an implicit animation
arrow.transform = CATransform3DRotate(
    arrow.transform, CGFloat(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.

In addition, a CAShapeLayer’s path, strokeStart, strokeEnd, 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 classes; the fact that a CAShapeLayer’s path can be animated is particularly intriguing, and I’ll give an example later in this chapter.)

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 generally 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.

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 collects all animation requests and hands them over to the animation server in a single batch. Every animation request takes place in the context of some 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 always an implicit transaction surrounding 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, CGFloat(M_PI)/4.0, 0, 0, 1)

A important use of transactions is to turn implicit animation off. This is valuable because implicit animation is the default, and can be unwanted (and a performance drag). To turn off implicit animation, call the CATransaction class method setDisableActions: with argument true. 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. Thus, it’s a way to be notified when any and all animations come to an end.

CATransaction implements KVC to allow you to set and retrieve a value for an arbitrary key, similar to CALayer.

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 initializer init(name:) 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,0.0) and (1.0,1.0), 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,0.0), whose control point is (0.42,0.0), and the other endpoint at (1.0,1.0), whose control point is (0.58,1.0) (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 init(controlPoints:). (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:

let clunk = CAMediaTimingFunction(controlPoints: 0.9, 0.1, 0.7, 0.9)
CATransaction.setAnimationTimingFunction(clunk)
arrow.transform = CATransform3DRotate(
    arrow.transform, CGFloat(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​A⁠nimation 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.

delegate

The delegate messages are animationDidStart: and animationDid⁠Stop:​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
For an infinite repeatCount (in Swift), use Float.infinity. 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 true, 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! The convenience initializer init(keyPath:) creates the instance and assigns it a keyPath.
additive
If true, 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". To animate a layer’s frame using explicit layer animation, if both its position and bounds are to change, you must animate both. Similarly, you cannot use explicit layer animation to animate a layer’s affineTransform property, because affineTransform is not a property (it’s a pair of convenience methods); you must animate its transform instead. Attempting to form an animation with a key path of "frame" or "affineTransform" is a common beginner error.

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 Objective-C objects, so numbers and structs will have to be wrapped accordingly, using NSNumber and NSValue (Swift will automatically take care of the former but not the latter). 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, of course, 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.

Note

The explicit animation is copied when it is added to the layer. Therefore the animation must be configured first and added to the layer later. Configuring an animation after it has been added to a layer will have no effect on how that layer is animated, because the animation that has been added to the layer is no longer the animation you are configuring.

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

// capture the start and end values
let startValue = arrow.transform
let endValue = CATransform3DRotate(
    startValue, CGFloat(M_PI)/4.0, 0, 0, 1)
// change the layer, without implicit animation
CATransaction.setDisableActions(true)
arrow.transform = endValue
// construct the explicit animation
let anim = CABasicAnimation(keyPath:"transform")
anim.duration = 0.8
let clunk = CAMediaTimingFunction(controlPoints:0.9, 0.1, 0.7, 0.9)
anim.timingFunction = clunk
anim.fromValue = NSValue(CATransform3D:startValue)
anim.toValue = NSValue(CATransform3D:endValue)
// ask for the explicit animation
arrow.addAnimation(anim, forKey:nil)

Once you’re comfortable with the full form, you will find that in many cases it can be condensed. For example, when the fromValue and toValue are not set, the former and current values of the property are used automatically. (This magic is possible because, at the time the CABasicAnimation is added to the layer, the presentation layer still has the former value of the property, while the layer itself has the new value; thus, the CABasicAnimation is able to retrieve them.) In our example, therefore, there is no need to set the fromValue and toValue, and no need to capture the start and end values beforehand. Here’s the condensed version:

CATransaction.setDisableActions(true)
arrow.transform = CATransform3DRotate(
    arrow.transform, CGFloat(M_PI)/4.0, 0, 0, 1)
let anim = CABasicAnimation(keyPath:"transform")
anim.duration = 0.8
let clunk = CAMediaTimingFunction(controlPoints:0.9, 0.1, 0.7, 0.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
let nowValue = arrow.transform
let startValue = CATransform3DRotate(
    nowValue, CGFloat(M_PI)/40.0, 0, 0, 1)
let endValue = CATransform3DRotate(
    nowValue, CGFloat(-M_PI)/40.0, 0, 0, 1)
// construct the explicit animation
let anim = CABasicAnimation(keyPath:"transform")
anim.duration = 0.05
anim.timingFunction = CAMediaTimingFunction(
    name:kCAMediaTimingFunctionLinear)
anim.repeatCount = 3
anim.autoreverses = true
anim.fromValue = NSValue(CATransform3D:startValue)
anim.toValue = NSValue(CATransform3D: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 true; 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 reference to 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:

let anim = CABasicAnimation(keyPath:"transform")
anim.duration = 0.05
anim.timingFunction = CAMediaTimingFunction(
    name:kCAMediaTimingFunctionLinear)
anim.repeatCount = 3
anim.autoreverses = true
anim.additive = true
anim.valueFunction = CAValueFunction(
    name: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.

Let’s return once more to our arrow “clunk” rotation for one final alternative implementation using the additive and valueFunction properties. We set the arrow layer to its final transform at the outset, so when the time comes to configure the animation, its toValue, in additive terms, will be 0; the fromValue will be its current value expressed negatively, like this:

let rot = CGFloat(M_PI)/4.0
CATransaction.setDisableActions(true)
arrow.transform = CATransform3DRotate(arrow.transform, rot, 0, 0, 1)
// construct animation additively
let anim = CABasicAnimation(keyPath:"transform")
anim.duration = 0.8
let clunk = CAMediaTimingFunction(controlPoints:0.9, 0.1, 0.7, 0.9)
anim.timingFunction = clunk
anim.fromValue = -rot
anim.toValue = 0
anim.additive = true
anim.valueFunction = CAValueFunction(name:kCAValueFunctionRotateZ)
arrow.addAnimation(anim, forKey:nil)

This is an interesting way of describing the animation; in effect, it expresses the animation in reverse, regarding the final position as correct and the current position as an aberration to be corrected. It also happens to be the way iOS 8 additive view animations are rewritten behind the scenes, and explains their behavior.

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 array.

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:

var values = [0.0]
var direction = 1.0
for (var i = 20; i < 60; i += 5, direction *= -1) { // alternate directions
    values.append( direction * M_PI / Double(i) )
}
values.append(0.0)
let anim = CAKeyframeAnimation(keyPath:"transform")
anim.values = values
anim.additive = true
anim.valueFunction = CAValueFunction(name: 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 CGPath. 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:

let anim = CAKeyframeAnimation(keyPath:"contents")
// self.images is an array of UIImage
anim.values = self.images.map {$0.CGImage as AnyObject}
anim.keyTimes = [0.0, 0.25, 0.5, 0.75, 1.0]
anim.calculationMode = kCAAnimationDiscrete
anim.duration = 1.5
anim.repeatCount = Float.infinity
// self.sprite is a CALayer
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 easily make that property animatable through a CAPropertyAnimation (a CABasicAnimation or a CAKeyframeAnimation). For example, here we animate the increase or decrease in a CALayer subclass property called thickness, using essentially the pattern for explicit animation that we’ve already developed:

let lay = self.v.layer as MyLayer
let cur = lay.thickness
let val : CGFloat = cur == 10 ? 0 : 10
lay.thickness = val
let ba = CABasicAnimation(keyPath:"thickness")
ba.fromValue = cur
lay.addAnimation(ba, forKey:nil)

To make our layer responsive to such a command, it needs a thickness property (obviously) and it must return true from the class method needsDisplayForKey:, where the key is the string name of the property:

class MyLayer : CALayer {
    var thickness : CGFloat = 0
    override class func needsDisplayForKey(key: String) -> Bool {
        if key == "thickness" {
            return true
        }
        return super.needsDisplayForKey(key)
    }
}

Returning true 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:

override func drawInContext(con: CGContext) {
    let r = self.bounds.rectByInsetting(dx:20, dy:20)
    CGContextSetFillColorWithColor(con, UIColor.redColor().CGColor)
    CGContextFillRect(con, r)
    CGContextSetLineWidth(con, self.thickness)
    CGContextStrokeRect(con, r)
}

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

We have made MyLayer’s thickness property animatable when using explicit layer animation, but it would be even cooler to make it animatable when using implicit layer animation (that is, when setting lay.thickness directly). Later in this chapter, I’ll show how to do that.

Grouped Animations

A grouped animation (CAAnimationGroup) combines multiple animations into one, by means of its animations property (an array 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 property values from their parent. Thus, for example, if you don’t set a child’s duration explicitly, it will inherit the parent’s duration.

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 (failing to do this, and then wondering why some child animations never occur, is a common beginner error):

// capture current value, set final value
let rot = M_PI/4.0
CATransaction.setDisableActions(true)
let current = arrow.valueForKeyPath("transform.rotation.z")!.doubleValue
arrow.setValue(current + rot, forKeyPath:"transform.rotation.z")
// first animation (rotate and clunk)
let anim1 = CABasicAnimation(keyPath:"transform")
anim1.duration = 0.8
let clunk = CAMediaTimingFunction(controlPoints:0.9, 0.1, 0.7, 0.9)
anim1.timingFunction = clunk
anim1.fromValue = current
anim1.toValue = current + rot
anim1.valueFunction = CAValueFunction(name:kCAValueFunctionRotateZ)
// second animation (waggle)
var values = [0.0]
var direction = 1.0
for (var i = 20; i < 60; i += 5, direction *= -1) { // alternate directions
    values.append( direction * M_PI / Double(i) )
}
values.append(0.0)
let anim2 = CAKeyframeAnimation(keyPath:"transform")
anim2.values = values
anim2.duration = 0.25
anim2.additive = true
anim2.beginTime = anim1.duration - 0.1
anim2.valueFunction = CAValueFunction(name: kCAValueFunctionRotateZ)
// group
let group = CAAnimationGroup()
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:

let h : CGFloat = 200
let v : CGFloat = 75
let path = CGPathCreateMutable()
var leftright : CGFloat = 1
var next : CGPoint = self.v.layer.position
var pos : CGPoint
CGPathMoveToPoint(path, nil, next.x, next.y)
for i in 0 ..< 4 {
    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)
}
let anim1 = CAKeyframeAnimation(keyPath:"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:

let revs = [0.0, M_PI, 0.0, M_PI]
let anim2 = CAKeyframeAnimation(keyPath:"transform")
anim2.values = revs
anim2.valueFunction = CAValueFunction(name:kCAValueFunctionRotateY)
anim2.calculationMode = kCAAnimationDiscrete

Here’s the third animation, the rocking of the boat. It has a short duration, and repeats indefinitely:

let pitches = [0.0, M_PI/60.0, 0.0, -M_PI/60.0, 0.0]
let anim3 = CAKeyframeAnimation(keyPath:"transform")
anim3.values = pitches
anim3.repeatCount = Float.infinity
anim3.duration = 0.5
anim3.additive = true
anim3.valueFunction = CAValueFunction(name: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:

let group = CAAnimationGroup()
group.animations = [anim1, anim2, anim3]
group.duration = 8
self.v.layer.addAnimation(group, forKey:nil)
CATransaction.setDisableActions(true)
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.

Freezing an Animation

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.

One remarkably powerful way to take advantage of this feature of CALayer is to assign a layer a speed of 0. This effectively “freezes” any animation attached to the layer. You can then change the layer’s timeOffset to display any single frame of the animation. In effect, the frozen animation has given you a whole slew of interpolated states “for free,” any of which you can select by setting the layer’s timeOffset.

To illustrate, let’s explore the animatable path property of a CAShapeLayer. Consider a layer that can display a rectangle or an ellipse or any of the intermediate shapes between them. I can’t imagine what the notion of an intermediate shape between a rectangle or an ellipse may mean, let alone how to draw such an intermediate shape; but thanks to frozen animations, I don’t have to. Here, I’ll construct the CAShapeLayer, add it to the interface, give it an animation from a rectangle to an ellipse, and keep a reference to it as a property:

let shape = CAShapeLayer()
shape.frame = v.bounds
v.layer.addSublayer(shape)
shape.fillColor = UIColor.clearColor().CGColor
shape.strokeColor = UIColor.redColor().CGColor
let path = CGPathCreateWithRect(shape.bounds, nil)
shape.path = path
let path2 = CGPathCreateWithEllipseInRect(shape.bounds, nil)
let ba = CABasicAnimation(keyPath: "path")
ba.duration = 1
ba.fromValue = path
ba.toValue = path2
shape.speed = 0
shape.timeOffset = 0
shape.addAnimation(ba, forKey: nil)
self.shape = shape

I’ve added the animation to the layer, but because the layer’s speed is 0, no animation takes place; the rectangle is displayed and that’s all. There’s also a UISlider in the interface. I’ll respond to the user changing the value of the slider by setting the frame of the animation:

@IBAction func doSlider(sender: AnyObject) { // slider action
    let slider = sender as UISlider
    self.shape.timeOffset = Double(slider.value)
}

This astonishing feature of layers and animations can be used in many powerful ways. It lies at the heart of interactive view controller transition animations (Chapter 6), and is probably used in unsuspected places throughout the iPhone and iPad interface.

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:

let t = CATransition()
t.type = kCATransitionPush
t.subtype = kCATransitionFromBottom
t.duration = 2
lay.addAnimation(t, forKey: nil)

The entire layer exits moving down from its original place while fading away, and another copy of the very same layer enters moving down from above while fading in. 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:

// ... configure the transition as before ...
CATransaction.setDisableActions(true)
lay.contents = UIImage(named: "Smiley")!.CGImage
lay.addAnimation(t, forKey: nil)

A common device is for the layer that is to be transitioned to be inside a superlayer that is exactly the same size and whose masksToBounds is true. 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 Figure 4-3, which shows a smiley face pushing an image of Mars out of the layer, I’ve emphasized this arrangement by giving the superlayer a border as well.

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 the .AllowAnimatedContent option for 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 order in which animations were added to the list is the order in which they are applied.

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. In iOS 8, additive view animations affecting the same property work around this limitation simply by giving the additional animations a different key name (for example, "position" and "position-2").

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 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 layer 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 true: when the “movie” is over, the animation removes itself from the list.

You can, if desired, set removedOnCompletion to false. 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 true.

Note

You may encounter examples that set removedOnCompletion to false 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. Feel free to skip this section if you don’t want to get into the under-the-hood nitty-gritty of implicit animation.

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. The only class that adopts the CAAction protocol is CAAnimation, but in fact the action object doesn’t have to be an animation — it doesn’t even have to perform an animation.

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

What an animation does when it receives run⁠Act⁠ion⁠For⁠Key:​obj⁠ect:⁠arg⁠um⁠en⁠ts: 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 run⁠Action⁠For⁠Key:​object:⁠arguments:, it calls addAnimation:forKey: to add itself to the layer’s animation’s list, using the name of the property as the key. The animation’s keyPath for an implicit layer animation is usually nil, so the animation’s keyPath winds up being set 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: the layer searches for an action object (a CAAction) to which it can send the runActionForKey:object:arguments: message. The procedure by which the layer searches for this animation is quite elaborate.

The search for an action object begins when something causes the layer to be sent the actionForKey: message. Three sorts of event can cause this to happen:

  • A specially marked CALayer property is set — by calling the setter method explicitly, by setting the property itself, or by means of setValue:forKey:. All animatable properties, and indeed most (or all) other CALayer properties, are marked in this special way. (You can mark a custom property in this same way by designating it as @dynamic in Objective-C, as I’ll demonstrate later in this chapter.)

    Setting a layer’s frame property sets its position and bounds and calls actionForKey: for the "position" and "bounds" keys. Calling a layer’s setAffineTransform: sets its transform and calls actionForKey: for the "transform" key.

  • The layer is sent setValue:forKey: with a key that is not a property. This is because CALayer’s setValue:forUndefinedKey:, by default, calls actionForKey:.
  • Various other miscellaneous types of event take place, such as the layer being added to the interface. I’ll give some examples later in this chapter.

Note

CATransaction’s setDisableActions:, with an argument of true, prevents the actionForKey: message from being sent. That’s how it actually works behind the scenes.

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 is produced, that is the end of the search. The action mechanism sends that action object the run⁠Act⁠ion⁠For⁠Key:object:​arg⁠um⁠en⁠ts: message; if this an animation, the animation responds by adding itself to the layer’s animations list.
NSNull()
If NSNull() is produced, that is the end of the search. There will be no implicit animation; NSNull() 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 action object or NSNull() 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 action object or NSNull() 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.

You can affect the action search at any of its various stages to modify what happens when the search is triggered.

For example, you can turn off implicit animation for some particular property. One way would be to return nil from actionForKey: itself, in a CALayer subclass. Here’s the code from a CALayer subclass that doesn’t animate its position property (but does animate its other properties normally):

override func actionForKey(key: String!) -> CAAction! {
    if key == "position" {
            return nil
        }
    return super.actionForKey(key)
}

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:

override func actionForKey(key: String!) -> CAAction! {
    if key == "position" {
        if self.valueForKey("suppressPositionAnimation") != nil {
            return nil
        }
    }
    return super.actionForKey(key)
}

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

layer.setValue(true, forKey:"suppressPositionAnimation")

Another possibility is to cause some stage of the search to produce an action object of your own. You would then be affecting how implicit animation behaves.

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:

let ba = CABasicAnimation()
ba.duration = 5

The idea now is to situate this animation where it will be produced by the action search for the "position" key. We could, for instance, put it into the layer’s actions dictionary:

layer.actions = ["position": ba]

The only property of this animation that we have set is its duration; that setting, however, is final. 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. Thus, when we set this 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.5) // won't work
layer.position = CGPointMake(100,100)

Storing an animation in the actions dictionary, however, is a somewhat inflexible way to hook into the action search. If we have to write our animation beforehand, we know nothing about the layer’s starting and ending values for the changed property. A much more powerful approach is to make our action object a custom CAAction object — because in that case, it will be sent runActionForKey:..., and we can construct and run an animation now, when we are in direct contact with the layer to be animated. Here’s a barebones version of such an object:

class MyAction : NSObject, CAAction {
    func runActionForKey(event: String!, object anObject: AnyObject!,
        arguments dict: [NSObject : AnyObject]!) {
            let anim = CABasicAnimation(keyPath: event)
            anim.duration = 5
            let lay = anObject as CALayer
            let newP : AnyObject? = lay.valueForKey(event)
            let oldP : AnyObject? = lay.presentationLayer()!.valueForKey(event)
            lay.addAnimation(anim, forKey:nil)
    }
}

The idea is that this would then be the action object that we store in the actions dictionary:

layer.actions = ["position": MyAction()]

Our custom CAAction object, MyAction, doesn’t do anything very interesting — but it could. That’s the point. As the code demonstrates, we have access to the name of the animated property (event), the old value of that property (from the layer’s presentation layer), and the new value of that property (from the layer itself). We are thus free to configure the animation in all sorts of ways. In fact, we can add more than one animation to the layer, or a group animation. We don’t have to add an animation to the layer! We are free to interpret the setting of this property in any way we like.

Here’s a modification of our MyAction object that creates and runs a keyframe animation that “waggles” as it goes from the start value to the end value:

class MyAction : NSObject, CAAction {
    func runActionForKey(event: String!, object anObject: AnyObject!,
        arguments dict: [NSObject : AnyObject]!) {
            let lay = anObject as CALayer
            let newP = (lay.valueForKey(event) as NSValue).CGPointValue()
            let oldP =
                (lay.presentationLayer()!.valueForKey(event) as NSValue)
                    .CGPointValue()
            let d = sqrt(pow(oldP.x - newP.x, 2) + pow(oldP.y - newP.y, 2))
            let r = Double(d/3.0)
            let theta = Double(atan2(newP.y - oldP.y, newP.x - oldP.x))
            let wag = 10*M_PI/180.0
            let p1 = CGPointMake(
                oldP.x + CGFloat(r*cos(theta+wag)),
                oldP.y + CGFloat(r*sin(theta+wag)))
            let p2 = CGPointMake(
                oldP.x + CGFloat(r*2*cos(theta-wag)),
                oldP.y + CGFloat(r*2*sin(theta-wag)))
            let anim = CAKeyframeAnimation(keyPath: event)
            anim.values = [oldP,p1,p2,newP].map{NSValue(CGPoint:$0)}
            anim.calculationMode = kCAAnimationCubic
            lay.addAnimation(anim, forKey:nil)
    }
}

By adding this CAAction object to a layer’s actions dictionary under the "position" key, we have created a CALayer that waggles when its position is set. The power of this mechanism is simply staggering. We can modify any layer in this way — even one that doesn’t belong to us.

Instead of modifying the layer’s actions dictionary, we could hook into the action search by setting the layer’s delegate to an instance that responds to actionForLayer:forKey:. This has the advantage of serving as a single locus that can do different things depending on what the layer is and what the key is. Here’s an implementation that does exactly what the actions dictionary did — it returns an instance of our custom CAAction object, so that setting the layer’s position waggles it into place:

override func actionForLayer(layer: CALayer!,
    forKey key: String!) -> CAAction! {
        if key == "position" {
            return MyAction()
        }
        return nil
}

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

override class func defaultActionForKey(key: String!) -> CAAction! {
    if key == "contents" {
        let tr = CATransition()
        tr.type = kCATransitionPush
        tr.subtype = kCATransitionFromLeft
        return tr
    }
    return super.defaultActionForKey(key)
}

Tip

Both the delegate’s actionForLayer:forKey: and the subclass’s defaultActionForKey: are declared as returning a CAAction. Therefore, to return NSNull() from your implemention of one of these methods, you’ll need to typecast it to CAAction to quiet the compiler; you’re lying (NSNull does not adopt the CAAction protocol), but it doesn’t matter.

Making a Custom Property Implicitly Animatable

Earlier in this chapter, we made a custom layer’s thickness property animatable through explicit layer animation. Now that we know how implicit layer animation works, we can make our layer’s thickness property animatable through implicit animation as well. Thus, we will be able to animate our layer’s thickness with code like this:

let lay = self.v.layer as MyLayer
let cur = lay.thickness
let val : CGFloat = cur == 10 ? 0 : 10
lay.thickness = val // implicit animation

We have already implemented needsDisplayForKey: to return true for the "thickness" key, and we have provided an appropriate drawInContext: implementation. Now we’ll add two further pieces of the puzzle. As we now know, to make our MyLayer class respond to direct setting of a property, we need to hook into the action search and return a CAAction. The obvious place to do this is in the layer itself, at the very start of the action search, in an actionForKey: implementation:

override func actionForKey(key: String!) -> CAAction! {
    if key == "thickness" {
        let ba = CABasicAnimation(keyPath: key)
        ba.fromValue = (self.presentationLayer() as CALayer).valueForKey(key)
        return ba
    }
    return super.actionForKey(key)
}

Finally, we must declare our thickness property @dynamic in the Objective-C sense — in fact, we must make this declaration in Objective-C (Swift’s dynamic is not the same thing). Otherwise, actionForKey: won’t be called in the first place (the action search will never happen). Thus, the MyLayer class is now declared in Objective-C:

// MyLayer.h:
@interface MyLayer : CALayer
@property CGFloat thickness;
@end
// MyLayer.m:
#import "MyLayer.h"
@implementation MyLayer
@dynamic thickness;
@end

The Swift code must import "MyLayer.h" in the bridging header, and is now an extension of the Objective-C class:

public extension MyLayer {
    // ... code goes here ...
}

Nonproperty Actions

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:

let layer = CALayer()
// ... configure layer here ...
layer.delegate = self
self.view.layer.addSublayer(layer)

In the layer’s delegate (self), we implement the actual animation as a group animation, fading the layer quickly in from an opacity of 0 and at the same time scaling its transform to make it momentarily appear a little larger:

override func actionForLayer(layer: CALayer!,
    forKey key: String!) -> CAAction! {
        if key == kCAOnOrderIn {
            let anim1 = CABasicAnimation(keyPath:"opacity")
            anim1.fromValue = 0.0
            anim1.toValue = layer.opacity
            let anim2 = CABasicAnimation(keyPath:"transform")
            anim2.toValue = NSValue(CATransform3D:
                CATransform3DScale(layer.transform, 1.2, 1.2, 1.0))
            anim2.autoreverses = true
            anim2.duration = 0.1
            let group = CAAnimationGroup()
            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 true is never played. A possible workaround is to trigger the animation in some other way (and remove the layer afterward, if desired).

Recall, for example, that an action search is triggered when an arbitrary key is set on a layer. Let’s implement the key "farewell" so that it shrinks and fades the layer and then removes it from its superlayer:

layer.delegate = self
layer.setValue("", forKey:"farewell")

The supplier of the action object — in this case, the layer’s delegate — returns the shrink-and-fade animation; it also sets itself as that animation’s delegate, and removes the layer when the animation ends:

override func actionForLayer(layer: CALayer!,
    forKey key: String!) -> CAAction! {
        if key == "farewell" {
            let anim1 = CABasicAnimation(keyPath:"opacity")
            anim1.fromValue = layer.opacity
            anim1.toValue = 0.0
            let anim2 = CABasicAnimation(keyPath:"transform")
            anim2.toValue = NSValue(CATransform3D:
                CATransform3DScale(layer.transform, 0.1, 0.1, 1.0))
            let group = CAAnimationGroup()
            group.animations = [anim1, anim2]
            group.duration = 0.2
            group.delegate = self // animationDidStop will be called
            group.setValue(layer, forKey:"remove") // identifier
            layer.opacity = 0
            return group
        }
}
override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
    if let layer = anim.valueForKey("remove") as? CALayer {
        layer.removeFromSuperlayer()
    }
}

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 CGImage) 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), false, 1)
let con = UIGraphicsGetCurrentContext()
CGContextAddEllipseInRect(con, CGRectMake(0,0,10,10))
CGContextSetFillColorWithColor(con, UIColor.grayColor().CGColor)
CGContextFillPath(con)
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// make a cell with that image
let cell = CAEmitterCell()
cell.birthRate = 5
cell.lifetime = 1
cell.velocity = 100
cell.contents = im.CGImage

(In the first line, we deliberately keep the scale at 1, even on a high-resolution screen, because a CAEmitterLayer has no contentsScale, as a CALayer does; 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:

let emit = CAEmitterLayer()
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.0,100.0), 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 = CGFloat(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 = 0.4
cell.velocityRange = 20
cell.scaleRange = 0.2
cell.scaleSpeed = 0.2
cell.color = UIColor.blueColor().CGColor
cell.greenRange = 0.5
cell.greenSpeed = 0.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.0, 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:

let key = "emitterCells.circle.greenSpeed"
let ba = CABasicAnimation(keyPath:key)
ba.fromValue = -1.0
ba.toValue = 3.0
ba.duration = 4
ba.autoreverses = true
ba.repeatCount = Float.infinity
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):

let cell2 = CAEmitterCell()
cell.emitterCells = [cell2]
cell2.contents = im.CGImage
cell2.emissionRange = CGFloat(M_PI)
cell2.birthRate = 200
cell2.lifetime = 0.4
cell2.velocity = 200
cell2.scale = 0.2
cell2.beginTime = 0.04
cell2.duration = 0.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 = 1.4
cell2.duration = 0.4
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 (more like Niagara Falls):

emit.emitterPosition = CGPointMake(100,25)
emit.emitterSize = CGSizeMake(100,100)
emit.emitterShape = kCAEmitterLayerLine
emit.emitterMode = kCAEmitterLayerOutline
cell.emissionLongitude = 3*CGFloat(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 a 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 properties, 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 a timestamp property, which we initialize as well:

let moi = CIImage(image:UIImage(named:"moi"))
self.moiextent = moi.extent()
let col = CIFilter(name:"CIConstantColorGenerator")
let cicol = CIColor(color:UIColor.redColor())
col.setValue(cicol, forKey:"inputColor")
let colorimage = col.valueForKey("outputImage") as CIImage
let tran = CIFilter(name:"CIFlashTransition")
tran.setValue(colorimage, forKey:"inputImage")
tran.setValue(moi, forKey:"inputTargetImage")
let center = CIVector(x:self.moiextent.width/2.0, y:self.moiextent.height/2.0)
tran.setValue(center, forKey:"inputCenter")
self.con = CIContext(options: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:

let link = CADisplayLink(target:self, 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 a property, 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:

func nextFrame(sender:CADisplayLink) {
    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) // * SCALE
    }
    sender.paused = true // defend against frame loss
    self.tran.setValue(self.frame, forKey:"inputTime")
    let moi = self.con.createCGImage(
        tran.outputImage, fromRect:self.moiextent)
    CATransaction.setDisableActions(true)
    self.v.layer.contents = moi
    if self.frame > 1.0 {
        sender.invalidate()
    }
    sender.paused = false
}

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 property (as I have shown in a comment). If you experiment with this code, run on the device, as display links do not work well in the Simulator.

UIKit Dynamics

The term UIKit dynamics refers to a suite of classes that 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, bouncing, 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. Like CIFilter animated transitions, UIKit dynamics relies on CADisplayLink, and the calculation of each frame takes place on the main thread (not on the animation server’s background thread). There’s no “animation movie” and no distinct presentation layer; the views really are being repositioned in real time. Thus, UIKit Dynamics is not intended for extended use; 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(referenceView: 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:

let grav = UIGravityBehavior()
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 serious 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 (a closure), which is called repeatedly as the animator drives the animation. I’ll configure our gravity behavior’s action block to check whether self.iv is still within the bounds of the reference view, by calling the animator’s itemsInRect: method. Here’s my first attempt:

grav.action = {
    let items = self.anim.itemsInRect(self.view.bounds) as [UIView]
    let ix = find(items, self.iv)
    if ix == nil {
        self.anim.removeAllBehaviors()
        self.iv.removeFromSuperview()
    }
}

This works in the sense that, after the image view leaves the screen, the image view is removed from the window and the animation stops. Unfortunately, there is also a memory leak: neither the image view nor the gravity behavior has been released. One solution is, in grav.action, to set self.anim (the animator property) to nil, thus breaking the retain cycle. This is a perfectly appropriate solution if, as here, we no longer need the animator for anything; a UIDynamicAnimator is a lightweight object and can very reasonably come into existence only for as long as we need to run an animation. Another possibility is to use delayed performance; even a delay of 0 solves the problem, presumably because the behavior’s action closure is no longer running at the time we remove the behavior:

grav.action = {
    let items = self.anim.itemsInRect(self.view.bounds) as [UIView]
    let ix = find(items, self.iv)
    if ix == nil {
        delay(0) {
            self.anim.removeAllBehaviors()
            self.iv.removeFromSuperview()
        }
    }
}

Now 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:

let push = UIPushBehavior(items:[self.iv], mode:.Instantaneous)
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:

let coll = UICollisionBehavior()
coll.collisionMode = .Boundaries
coll.addBoundaryWithIdentifier("floor",
    fromPoint:CGPointMake(0, self.view.bounds.height),
    toPoint:CGPointMake(self.view.bounds.width,
        self.view.bounds.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:

let bounce = UIDynamicItemBehavior()
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:

func collisionBehavior(behavior: UICollisionBehavior,
    beganContactForItem item: UIDynamicItem,
    withBoundaryIdentifier identifier: NSCopying,
    atPoint p: CGPoint) {
        // look for the dynamic item behavior
        for b in self.anim.behaviors as [UIDynamicBehavior] {
            if let bounce = b as? UIDynamicItemBehavior {
                let v = bounce.angularVelocityForItem(self.iv)
                if v <= 0.1 {
                    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(view:self.iv))

All the work is now done by the MyDropBounceAndRollBehavior instance. I’ve designed it to affect just one view, so its initializer looks like this:

init(view v:UIView) {
    self.v = v
    super.init()
}

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 the rest of MyDropBounceAndRollBehavior. Our precautions in the gravity behavior’s action block not to cause a retain cycle are simpler than before; it suffices to designate self as a weak reference and remove self from the animator explicitly:

override func willMoveToAnimator(anim: UIDynamicAnimator!) {
    if anim == nil { return }
    let sup = self.v.superview!
    let grav = UIGravityBehavior()
    grav.action = {
        [weak self] in
        let items = anim.itemsInRect(sup.bounds) as [UIView]
        if find(items, self!.v) == nil {
            anim.removeBehavior(self)
            self!.v.removeFromSuperview()
        }
    }
    self.addChildBehavior(grav)
    grav.addItem(self.v)
    let push = UIPushBehavior(items:[self.v], mode:.Instantaneous)
    push.pushDirection = CGVectorMake(2, 0)
    self.addChildBehavior(push)
    let coll = UICollisionBehavior()
    coll.collisionMode = .Boundaries
    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)
    let bounce = UIDynamicItemBehavior()
    bounce.elasticity = 0.4
    self.addChildBehavior(bounce)
    bounce.addItem(self.v)
}
func collisionBehavior(behavior: UICollisionBehavior,
    beganContactForItem item: UIDynamicItem,
    withBoundaryIdentifier identifier: NSCopying,
    atPoint p: CGPoint) {
        // look for the dynamic item behavior
        for b in self.childBehaviors as [UIDynamicBehavior] {
            if let bounce = b as? UIDynamicItemBehavior {
                let 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 true, 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 true.

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 the image view spinning clockwise by means of its initial push, like this:

push.setTargetOffsetFromCenter(UIOffsetMake(0, -200), forItem:self.v)
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 or to a point in the reference view , depending on how you initialize it:

  • init(item:attachedToItem:)
  • init(item:attachedToAnchor:)

The attachment point is, by default, the item’s center; to change that, there’s a different pair of initializers:

  • init(item:offsetFromCenter:attachedToItem:offsetFromCenter:)
  • init(item: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 the 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

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 (parallax). When an alert is present, for example, if the user tilts the device, the alert shifts its position; the effect is subtle, but sufficient to suggest subconsciously that the alert 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 of its view that 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:

let m1 = UIInterpolatingMotionEffect(
    keyPath:"center.x", type:.TiltAlongHorizontalAxis)
m1.maximumRelativeValue = 10.0
m1.minimumRelativeValue = -10.0
let m2 = UIInterpolatingMotionEffect(
    keyPath:"center.y", type:.TiltAlongVerticalAxis)
m2.maximumRelativeValue = 10.0
m2.minimumRelativeValue = -10.0
let g = UIMotionEffectGroup()
g.motionEffects = [m1,m2]
v.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. If you do, 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:

UIView.animateWithDuration(1, animations:{
    self.v.center.x += 100
    }, completion: {
        _ in
        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:

let con = self.v_horizontalPositionConstraint
con.constant += 100
UIView.animateWithDuration(1, animations:{
    self.v.layoutIfNeeded()
})

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:

let snap = self.v.snapshotViewAfterScreenUpdates(true)
snap.frame = self.v.frame
self.v.superview!.addSubview(snap)
self.v.hidden = true
UIView.animateWithDuration(1, animations:{
    snap.center.x += 100
})

That works because the snapshot view is not under the influence of autolayout, so it stays where we put it even if layout takes place. If, however, we need to remove the snapshot view and reveal the real view, and 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.

Obviously, a view that is not under the direct influence of autolayout can be animated however you like without violating any constraints. Thus, yet another possibility is to remove the animated view from the influence of autolayout (remove its constraints and set its translatesAutoresizingMaskIntoConstraints to true).

It is unfortunate that such elaborate tactics are needed. 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 grappling with it, has studiously glossed over it ever since.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required