This tutorial looks at some more animation functions that require a little bit more setting up to create, but once mastered opens up an even larger number of possibilities.

This alone can create an interesting range of animations, but is still only the tip of the iceberg of what Apple provides as tools for creating animations.

In Prototyping iOS Animations in Swift I introduced the a range of block-based functions that UIKit provides to create tweened animations and how a simple animation can be programmatically altered with some random variation to create more complex scenes.

Container view transitions

If you want to perform an animated transition between two views, Apple provides a handful of default animations that are easy to create with just a small bit of code.

The trick to using these methods is that the transition needs to be performed in a parent container, which is typically just an invisible UIView that’s the size of the largest object you’re transitioning with. So performing this animation requires a little bit of setting up.

For example, to animate a transition between two colored UIViews we’ll use a third UIView as the container for the animation.

Lets set up all three views in the viewDidLoad() function…

let container = UIView() let redSquare = UIView() let blueSquare = UIView() override func viewDidLoad() { super.viewDidLoad() // set container frame and add to the screen self.container.frame = CGRect(x: 60, y: 60, width: 200, height: 200) self.view.addSubview(container) // set red square frame up // we want the blue square to have the same position as redSquare // so lets just reuse blueSquare.frame self.redSquare.frame = CGRect(x: 0, y: 0, width: 200, height: 200) self.blueSquare.frame = redSquare.frame // set background colors self.redSquare.backgroundColor = UIColor.redColor() self.blueSquare.backgroundColor = UIColor.blueColor() // for now just add the redSquare // we'll add blueSquare as part of the transition animation self.container.addSubview(self.redSquare) }

Running the app now all you should see is a redSquare:

Now lets add an @IBAction from a button in the storyboard to trigger the actual animation and use the function UIView.transitionWithView which takes these options:

view: the view that the transition is animated within

duration: the number of seconds the transition will take

options: options for the transition (e.g. the animation style to use)

animations: a block defining changes to make as part of the transition; and

completion: another block this one defining code to run when the animation has completed.

The animation block should typically include the removal of one view, and the addition of the view (to the container) that is replacing it.

@IBAction func animateButtonTapped(sender: AnyObject) { // create a 'tuple' (a pair or more of objects assigned to a single variable) let views = (frontView: self.redSquare, backView: self.blueSquare) // set a transition style let transitionOptions = UIViewAnimationOptions.TransitionCurlUp UIView.transitionWithView(self.container, duration: 1.0, options: transitionOptions, animations: { // remove the front object... views.frontView.removeFromSuperview() // ... and add the other object self.container.addSubview(views.backView) }, completion: { finished in // any code entered here will be applied // .once the animation has completed }) }

The transition from red square to blue square works as expected, but after that we’re stuck with the blue square.

This is because our @IBAction function is set up with the expectation that the redSquare is the visible square, and this is only true the first time we tap the animate button.

To fix this we’ll need to add some conditional logic that checks to see which square is currently visible to figure out if we should the transitioning from red-to-blue, or blue-to-red.

There are lots of ways this could be done, but lets use a feature of Swift called a ‘tuple’.

// create a 'tuple' (a pair or more of objects assigned to a single variable) var views : (frontView: UIView, backView: UIView) // if redSquare has a superView (e.g it's in the container) // set redSquare as front, and blueSquare as back // otherwise flip the order if(self.redSquare.superview){ views = (frontView: self.redSquare, backView: self.blueSquare) } else { views = (frontView: self.blueSquare, backView: self.redSquare) }

Now we can alternate between the red and blue views!

Switching from one view to another is such a common task that so long as we don’t have anything else we also want to animate with the transition, Apple provides a slightly easier function that does the removeFromSuperview() and addSubView() for you automatically.

Here’s our simplified @IBAction function using this simplified transition function:

@IBAction func animateButtonTapped(sender: AnyObject) { // create a 'tuple' (a pair or more of objects assigned to a single variable) var views : (frontView: UIView, backView: UIView) if(self.redSquare.superview){ views = (frontView: self.redSquare, backView: self.blueSquare) } else { views = (frontView: self.blueSquare, backView: self.redSquare) } // set a transition style let transitionOptions = UIViewAnimationOptions.TransitionCurlUp // with no animation block, and a completion block set to 'nil' this makes a single line of code UIView.transitionFromView(views.frontView, toView: views.backView, duration: 1.0, options: transitionOptions, completion: nil) }

Now let’s try out some of the different default transition options that are available:

let transitionOptions = UIViewAnimationOptions.TransitionCurlDown

let transitionOptions = UIViewAnimationOptions.TransitionFlipFromLeft

Keyframe block animations

Another new addition in iOS 7, instead of creating an animation by interpolating between a start value to an end value, this method allows us to define an by as many sub-parts as we want.

For one example of why you’d want to do this, consider how you would rotate an image a full 360 degrees.

Using our familiar basic animation functions you could attempt to animate the transform property like so:

// create and add blue-fish.png image to screen let fish = UIImageView() fish.image = UIImage(named: "blue-fish.png") fish.frame = CGRect(x: 50, y: 50, width: 50, height: 50) self.view.addSubview(fish) // angles in iOS are measured as radians PI is 180 degrees so PI × 2 is 360 degrees let fullRotation = CGFloat(M_PI * 2) UIView.animateWithDuration(1.0, animations: { // animating `transform` allows us to change 2D geometry of the object // like `scale`, `rotation` or `translate` self.fish.transform = CGAffineTransformMakeRotation(fullRotation) })

But iOS can’t interpolate between the start and end values because they are equivalent!

To get around this, we’ll use animateKeyframesWithDuration to define the rotation in smaller parts, which iOS won’t get confused with, and then combine them all into a single animation.

To do the full rotation, lets break the animation into three parts, with each part rotating one third of the way around:

let duration = 2.0 let delay = 0.0 let options = UIViewKeyframeAnimationOptions.CalculationModeLinear UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: { // each keyframe needs to be added here // within each keyframe the relativeStartTime and relativeDuration need to be values between 0.0 and 1.0 UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: { // start at 0.00s (5s × 0) // duration 1.67s (5s × 1/3) // end at 1.67s (0.00s + 1.67s) self.fish.transform = CGAffineTransformMakeRotation(1/3 * fullRotation) }) UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/3, animations: { self.fish.transform = CGAffineTransformMakeRotation(2/3 * fullRotation) }) UIView.addKeyframeWithRelativeStartTime(2/3, relativeDuration: 1/3, animations: { self.fish.transform = CGAffineTransformMakeRotation(3/3 * fullRotation) }) }, completion: {finished in // any code entered here will be applied // once the animation has completed }) }

Now iOS has enough information to create the animation as we expected.

If you’re manually entering values for relativeStartTime & relativeDuration it’s easy to make a mistake, but if all you want to achieve is a smooth transition between each of the keyframes, you can enter CalculationModePaced as an option which ignores any values you’ve entered for relativeStartTime and relativeDuration and automatically figures out correct values for a consistent animation:

let duration = 2.0 let delay = 0.0 let options = UIViewKeyframeAnimationOptions.CalculationModePaced UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: { // note that we've set relativeStartTime and relativeDuration to zero. // Because we're using `CalculationModePaced` these values are ignored // and iOS figures out values that are needed to create a smooth constant transition UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0, animations: { self.fish.transform = CGAffineTransformMakeRotation(1/3 * fullRotation) }) UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0, animations: { self.fish.transform = CGAffineTransformMakeRotation(2/3 * fullRotation) }) UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0, animations: { self.fish.transform = CGAffineTransformMakeRotation(3/3 * fullRotation) }) }, completion: nil)

Moving an object along a bezier curve

A really fun animation to create is move the position of an object along a curve.

Our basic animation techniques make it easy to animate an object moving from point A to B, but to have an object move along the multiple points of a curve A,B,C,D,E we’ll need to use a keyframe-based animation again.

You could do this manually using the keyframe block function like we did for the rotation animation, but to get a nice smooth animation we’d have to define a lot of keyframes and it would get quickly get very complicated and messy.

Luckily, instead of assigning each keyframe manually, we can give iOS a bezier curve and the keyframes needed will be generated automatically.

This requires us to use more powerful animation features of iOS that are slightly more complicated, but not too hard once you get the general approach.

// first set up an object to animate // we'll use a familiar red square let square = UIView() square.frame = CGRect(x: 55, y: 300, width: 20, height: 20) square.backgroundColor = UIColor.redColor() // add the square to the screen self.view.addSubview(square) // now create a bezier path that defines our curve // the animation function needs the curve defined as a CGPath // but these are more difficult to work with, so instead // we'll create a UIBezierPath, and then create a // CGPath from the bezier when we need it let path = UIBezierPath() path.moveToPoint(CGPoint(x: 16,y: 239)) path.addCurveToPoint(CGPoint(x: 301, y: 239), controlPoint1: CGPoint(x: 136, y: 373), controlPoint2: CGPoint(x: 178, y: 110)) // create a new CAKeyframeAnimation that animates the objects position let anim = CAKeyframeAnimation(keyPath: "position") // set the animations path to our bezier curve anim.path = path.CGPath // set some more parameters for the animation // this rotation mode means that our object will rotate so that it's parallel to whatever point it is currently on the curve anim.rotationMode = kCAAnimationRotateAuto anim.repeatCount = Float.infinity anim.duration = 5.0 // we add the animation to the squares 'layer' property square.layer.addAnimation(anim, forKey: "animate position along path")

Now we have a single animation, lets use it multiple times to create a more complex scene.

// loop from 0 to 5 for i in 0...5 { // create a square let square = UIView() square.frame = CGRect(x: 55, y: 300, width: 20, height: 20) square.backgroundColor = UIColor.redColor() self.view.addSubview(square) // randomly create a value between 0.0 and 150.0 let randomYOffset = CGFloat( arc4random_uniform(150)) // for every y-value on the bezier curve // add our random y offset so that each individual animation // will appear at a different y-position let path = UIBezierPath() path.moveToPoint(CGPoint(x: 16,y: 239 + randomYOffset)) path.addCurveToPoint(CGPoint(x: 301, y: 239 + randomYOffset), controlPoint1: CGPoint(x: 136, y: 373 + randomYOffset), controlPoint2: CGPoint(x: 178, y: 110 + randomYOffset)) // create the animation let anim = CAKeyframeAnimation(keyPath: "position") anim.path = path.CGPath anim.rotationMode = kCAAnimationRotateAuto anim.repeatCount = Float.infinity anim.duration = 5.0 // add the animation square.layer.addAnimation(anim, forKey: "animate position along path") }

Now we have multiple squares animating, but they all start at the same time so don’t look so great.

Lets set some more properties to the animation object that adds some randomness to how long the animation takes (so that some of the squares will move faster than others), and at what position of the animation it starts (so that they appear staggered).

// each square will take between 4.0 and 8.0 seconds // to complete one animation loop anim.duration = Double(arc4random_uniform(40)+30) / 10 // stagger each animation by a random value // `290` was chosen simply by experimentation anim.timeOffset = Double(arc4random_uniform(290))

Now our squares follow a bezier curve with with much more variation due to the randomness we’ve added to each animation.

From here, it’s not too much of a step to switch the redSquares to images and add a background to create an interesting organic animation like a school of fish or a flock of birds.

Animating appearance of a bezier curve

Another useful technique to know that also uses an curve it to animate how much of the curve is drawn.

When we animated an object along a curve, the bezier path wasn’t actually shown on the screen, instead it was used as an input to the keyframe animation.

In this example, we’ll actually draw the curve to the screen, but animate how much of the curve is shown from 0 to 100%.

Lets add this code into an @IBAction function that’s triggered when a button is tapped.

// set up some values to use in the curve let ovalStartAngle = CGFloat(90.01 * M_PI/180) let ovalEndAngle = CGFloat(90 * M_PI/180) let ovalRect = CGRectMake(97.5, 58.5, 125, 125) // create the bezier path let ovalPath = UIBezierPath() ovalPath.addArcWithCenter(CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)), radius: CGRectGetWidth(ovalRect) / 2, startAngle: ovalStartAngle, endAngle: ovalEndAngle, clockwise: true) // create an object that represents how the curve // should be presented on the screen let progressLine = CAShapeLayer() progressLine.path = ovalPath.CGPath progressLine.strokeColor = UIColor.blueColor().CGColor progressLine.fillColor = UIColor.clearColor().CGColor progressLine.lineWidth = 10.0 progressLine.lineCap = kCALineCapRound // add the curve to the screen self.view.layer.addSublayer(progressLine) // create a basic animation that animates the value 'strokeEnd' // from 0.0 to 1.0 over 3.0 seconds let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") animateStrokeEnd.duration = 3.0 animateStrokeEnd.fromValue = 0.0 animateStrokeEnd.toValue = 1.0 // add the animation progressLine.addAnimation(animateStrokeEnd, forKey: "animate stroke end animation")

This animation uses a very simple oval as a curve, but this technique can be applied to any curve at all. I’ve seen examples where someone has converted cursive text to create the illusion of a word being written, or you could combine it with animating an object along the same curve to show the path it’s taken while animating.

System default animation(s)

Another addition with iOS 7 is UIView.performSystemAnimation which for now only has UISystemAnimation.Delete as a valid option but I’m hoping that in the future Apple will add more standard system animations that can be easily created using this function.

// create and add blue-fish.png image to screen let fish = UIImageView() fish.image = UIImage(named: "blue-fish.png") fish.frame = CGRect(x: 50, y: 50, width: 50, height: 50) self.view.addSubview(fish) // create an array of views to animate (in this case just one) let viewsToAnimate = [fish] // perform the system animation // as of iOS 8 UISystemAnimation.Delete is the only valid option UIView.performSystemAnimation(UISystemAnimation.Delete, onViews: viewsToAnimate, options: nil, animations: { // any changes defined here will occur // in parallel with the system animation }, completion: { finished in // any code entered here will be applied // once the animation has completed })

Fin

Thank you Paul Webb for catching my bad Fin/fish joke in my last post.

These animations start to get a little more complex so you might not succeed the first time around, but if you’re ever stuck, post a link to your code and I’d be happy to try and help!

Notes