Bring Your App To Life with CALayers

Many times, the most memorable part of an app is an intricate, animated experience. iOS provides a number of tools to create these experiences, but it can take some work to get the most out of them.

The basis for many great experiences lies in Core Animation. Using tools such as CALayer and CATransaction we can create a high resolution, vector-based, animated sequence that delights users. In this talk, we’ll use code samples to go through different animations and discuss how you can use these techniques in your app.

Let’s use the power of CALayers to make your app memorable.


Introduction (0:00)

My talk has two parts. The first part is taking a complex interaction and getting it into a state where we can animate it. This part is half retrospective. It’s how we’ve done something before, how it worked out, and how you can do it next time. The second part is about animations, in particular, Core Animation, which I think is not as hard as many people might think. It’s also a lot of fun.

How it all started (0:53)

It all started when a different iOS developer at Fitbit, named Aidan, and a designer on our team, Drew Matthews, came up with this really fun interaction for our sleep screen. This little interaction comes up whenever we were introducing sleep goals, but also whenever you start “sleep” in the app. It’s this really delightful, little, happy moon.

When we were working on some of the other features in the app a little bit later, we were thinking, “Okay, what could we use to really spice this up?” We wanted to create an onboarding experience around hourly activity and reminders to move, both features that came out alongside the FitBit Alta. It keeps track of how much you’ve moved every hour during a certain window and also helps remind you when to move so you can get 250 steps to keep active. The last thing we wanted to do was create another set of onboarding screens that nobody wanted to read.

The first thing to keep in mind is that at the top-right we added a “done” button because even though I would like people to really enjoy this, sometimes you don’t want to go through onboarding. Besides that, our illustrators came up with these really cool, little illustrations. The benefit of working with Drew on this is he just came along and said, “How about we do something cool with it,” and he put together a movie in After Effects.

We now had something to start with, and a goal of what we wanted to make it look like. Our first question was, “Okay, that’s cool, but how in the world do I build this?” There are a number of different ways. We’re going to talk how we did it for this interaction in Fitbit, and I’ll also mention a few ways you can explore other solutions as well.

Sketch and PaintCode (3:01)

The way that we built the animations and interactions was using Sketch, then going into PaintCode, and finally going into Xcode via code.

Sketch, as many of you might have worked with, is a vector-based tool. It’s very popular in mobile development. We have these illustrations made by an illustrator, we cleaned them up and exported them via SVG, which is a standard vector format that we can take and import into PaintCode.

PaintCode is a really cool tool for doing several things. One of the main things that it’s great at is moving interactive controls via code. It’s a WYSIWYG editor, and you can design all these things in it and then output direct code, no assets required. It also has tools for easily adjusting anchor points, bounds, relative locations, grouping things, etc.

This was a really decent solution for what we were looking for, though there are a few cons. First, it’s great for controls, but what we wanted was sequential animations. Second, its default implementation relies on drawRect, which doesn’t have a whole lot of leeway for state.

Another thing is that it imposes a third-party tool for getting your designs into your actual application because you go through this editor which outputs code. Lastly, what I discovered in hindsight, is that there’s a chance that if you have really complex shapes and interactions, it can actually increase your compile time just a little bit for each file because they can get rather large.

Let’s talk a little bit about some of the handy features. First is grouping. It’s really helpful when you have a whole bunch of different shapes, particularly ones you might want to animate together, but also separately. This is just a sample of maybe 1% of the generated code that PaintCode will output whenever you’re taking your files or your vector input and actually outputting your code.

- (void)drawIntroScreenWithFrame: (CGRect)frame rightArmAngle: (CGFloat)rightArmAngle
{
	//// General Declarations
	CGContextRef context = UIGraphicsGetCurrentContext();
	
	...

		//// pictureFrameGroup
	{
		//// pictureBackground Drawing
		UIBezierPath* pictureBackgroundPath = [UIBezierPath bezierPathWithRect:CGRectMake(
			CGRectGetMinX(pictureFrameGroup) + 3.27,
			CGRectGetMinY(pictureFrameGroup) + 1.48,
			122.75,
			94.65)];
		[pictureBackgroundBlue setFill];
		[pictureBackgroundPath fill];

		...

As you can see on drawRect, it takes in a few parameters for variables that we can define in PaintCode. But that brings us to CALayers.

CALayers (5:45)

What we want to do in this case is not rely on drawRect. We want to take our output from PaintCode, convert it into using CALayers, and animate those sequentially. We’ll go through a few steps to do that.

  • The first thing you do, it is crucial to clean up your vectors. When someone’s drawing something in Illustrator or Sketch or anything similar, sometimes there are tricks that you can use to hide things or to clips things, etc. When you’re outputting code, it might not be useful or performant if you have a whole bunch of hidden shapes in your file. Delete unused shapes and merge contiguous shapes with others so that you have one path as opposed to twenty.
  • You should also name your colors and layers; it will save you a lot of frustration in the end when you’re looking at your code. That way it’s mostly human readable, and instead of “color 37”, it might be “dark blue”.
  • One of the other things that we’re going to do is break down and transform our generator code. We’re going to create a custom view with a custom CALayer, this custom CALayer is going to expose a number of different animatable groups, and we’re going to manipulate our PaintCode output into this form. What does that mean? Let’s look at some actual code.
class OnboardingLayer : CALayer {
	let pictureFrame: CAShapeLayer
	let chair: CAShapeLayer
	let body: CAShapeLayer
	let leftArm: CAShapeLayer
	let rightArm: CAShapeLayer
	let table: CAShapeLayer
	let lamp: CAShapeLayer
}

Here we have our custom CALayer, and it has a bunch of different sub-shape layers beneath it. In this case, it’s the first scene of our onboarding experience.

I have a bunch of different elements that I want to animate: a picture frame, a chair, a person, a left and a right arm that I want to animate separately, etc. I’m going to configure these different shape layers using the bounds, the anchors, and the positions that I get from PaintCode. One thing you can do inside PaintCode is move the anchor position, and use that to set the anchor position for your CALayer. It’s one of the benefits of doing CALayer animations.

The next thing you want to do in this process, and this is a little bit odd; you want to break up your groups. Now this is because we’re transforming PaintCode’s output. We are doing a few things that it doesn’t necessarily, and normally, want to do. Typically it does a whole bunch of offsets in drawRect, but we don’t want any of those offsets built in.

So, again, what we’re going to do with our output is create a CALayer for each shape. Then, we’re going to set the path of the CALayer to the vector output for that actual shape. Finally, we’re going to place fill, which you typically use for drawRect, the fill color for the shape, and then we’re going to add that CALayer to the paint layer. We’re slowly building up our scene.

override init() {
	pictureFrame = OnboardingLayer.createPictureFrame()
	pictureFrame.anchorPoint = CGPointMake(0.5, 0);
	pictureFrame.bounds = CGRectMake(0, 0, 127, 98);
	pictureFrame.position = CGPointMake(64, 0);
	...

	super.init()

	bounds = CGRectMake(0, 0, 296, 241);
	addSublayer(pictureFrame)
	addSublayer(chair)
	addSublayer(table)
	addSublayer(lamp)
	...
}

Let’s talk about the picture frame and how things get built up. First, we’re going to create the picture frame layer. We’re going to do this for one of the sub-shape layers and set its anchor point, its bounds and its position. You can get that from looking at PaintCode’s outputted code and also its GUI. We’re going to set the bounds of the custom layer itself and add each of those shape layers that we created. What does that createPictureFrame() layer look like?

static func createPictureFrame() -> CAShapeLayer {
	let pictureFrameLayer = CAShapeLayer()

	// Color Declarations taken from paintcode
	let pictureBackgroundBlue = UIColor(
		red: 0.066, green: 0.485, blue: 0.695, alpha: 1.000)
	...

	// pictureBackground Layer Shape
	let pictureBackgroundLayer = CAShapeLayer()
	pictureBackgroundLayer.path = UIBezierPath(rect:
		CGRect(x: 3.27, y: 1.48, width: 122.75, height: 94.65)).CGPath
	pictureBackgroundLayer.fillColor = pictureBackgroundBlue.CGColor
	pictureFrameLayer.addSublayer(pictureBackgroundLayer)
	
	...

	return pictureFrameLayer
}

Simply, we create a shape layer. Then we configure any of our colors, and this is where naming your colors and your layers is helpful. A lot of this code comes from PaintCode, so we have a whole bunch of different colors.

For example, maybe a pictureBackgroundBlue. Then we’re going to take our output code from PaintCode and convert it into a shape layer, one for each shape within our picture frame. We’re going to set its fillColor, set it to path, and then we’re going to add it. We’re going to do this for each part of our shape layers.

Now, this can be a little bit tedious. This is part of the retrospective. If you’re going to make PaintCode work in ways that it’s not really meant to, you have to be willing to either think about scripting, using other tools, or be willing to put in a little bit of extra tedious work.

Where did that code come from? (10:54)

Where did that code come from? In the PaintCode UI there is a large panel that has all of your output code. You can select Objective-C or Swift, depending on what your compatibility is, and you take that and start breaking it down.

Earlier I mentioned that we wanted to break down our groups. If you don’t break down your groups, there’s a bunch of stuff in your output code that looks like this:

//// rightArmGroup
CGContextSaveGState(context)
CGContextTranslateCTM(context, 186.17, 82.7)
CGContextRotateCTM(context, -60.12 * CGFloat(M_PI) / 180)

//// rightarm Drawing
let rightarmPath = UIBezierPath()
rightarmPath.moveToPoint(CGPoint(x: 44.94, y: -59.59))

The way that it typically works using drawRect is that it will shift and transform in order to place things that are relative to groups, but because we’re breaking things down and building our CALayers that have their own position and bounds, that’s going to mess us up a little bit. Just remember, if you’re using this technique to break down your groups.

The important part in all of this is that we want to rinse and repeat that whole process, which is not the most fun thing in the world, but it is rather effective. What we have at the end is a nice, rendered illustration that could have taken us maybe two minutes to do as an image, but the really fun part is that each one of these is vector rendered, so we don’t have to worry about device size. We can animate things individually with different anchor points and it’s ready to go.

For a little bit of retrospective, however, there’s a number of different ways to get to this state:

  • One is that you can experiment with SpriteKit.
  • Another one is that there are several different tools, one of which is QuartzCode. QuartzCode does something very similar to PaintCode, except instead of having to translate things from drawRect into CALayer, QuartzCode will actually output CALayers. Now I haven’t worked with QuartzCode much myself, so I can’t say if it works well or not, but it’s another alternative.
  • Another one is Squall. Squall does a similar thing, except it goes directly from After Effects to code. If you have something that’s built in After Effects, that’s something to consider, but not many of us are very proficient in After Effects.

CATransaction - How to make it move (13:10)

Now we get on to the fun part. We’ve done all of this work, and we have our shapes and our layers all ready to go for animation. Let’s talk a little bit about how we’re going to make it move.

The first technique we’re going to talk about is CATransactions. Now CATransactions allow us to adjust properties one after another and have them perform in a batch in an animated fashion. We’re going to talk about how we’re going to do this first little animation.

We have everything come in from the left and the right side. We’ll do some of the other animations as we go, but right now we’re going to talk about how the chair, the picture frame, and the person come in from the left, and the lamp and the other side table come in from the right.

class IntroView : UIView {
	static let onboardingIntroArmAngle = CGFloat(0.610865)
	let illustrationLayer: OnboardingLayer = OnboardingLayer()
	var introAnimationOffset = CGFloat(0)

	func configureForIntroAnimation() {
		introAnimationOffset = (bounds.size.width / 2.0)
			+ (illustrationLayer.bounds.size.width / 2)

		illustrationLayer.chair.updatePosition(
			offset: CGPointMake(-introAnimationOffset, 0))
		illustrationLayer.pictureFrame.updatePosition(
			offset: CGPointMake(-introAnimationOffset, 0))
		// ...

We have our custom view called IntroView and it has our custom CALayer. We’re going to configure it at the beginning for our intro animation. To start, we’re going to take the bounds of our view, divide it in two, and move things to the left and right.

Now, when you apply something to a CALayer, it will directly apply it to that layer. Before we render, we’re going to configure it in the center and then immediately move it to the left and the right. You’ll note that the chair and picture frame we set it to offset with this handy little method called updatePosition, by just adjusting the position of the CALayer.

illustrationLayer.pictureFrame.transform =
		CATransform3DMakeRotation(CGFloat(20).degreesToRadians, 0, 0, 1);
	illustrationLayer.leftArm.transform =
		CATransform3DMakeRotation(-IntroView.onboardingIntroArmAngle, 0, 0, 1);
	illustrationLayer.rightArm.transform =
		CATransform3DMakeRotation(IntroView.onboardingIntroArmAngle, 0, 0, 1);
}

We’re going to do the same thing for the picture frame, the left arm, and the right arm. But for those, we’re going to set them at an initial angle because you’ll notice they move a little bit. We’re going to do that using CATransforms.

func startIntroAnimation() {
	CATransaction.begin()
	CATransaction.setAnimationDuration(0.5)
	CATransaction.setAnimationTimingFunction(
		CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut))

	illustrationLayer.chair.updatePosition(
		offset: CGPointMake(introAnimationOffset, 0))
	illustrationLayer.pictureFrame.updatePosition(
		offset: CGPointMake(introAnimationOffset, 0))

	...

	CATransaction.commit()
}

The fun part of this is animating. For that, we have startIntroAnimation. You can begin a CATransaction, and you can set many properties that you might be more familiar with using UIView Animations on your CATransaction. You could set a duration, so in this case, half a second. You can set timing functions, ease in, ease out, etc.

Then within your CATransaction, adjust the properties on your CALayers and commit the transaction. In this case, we’re going to set the position of our elements back to the center by making their offset the reserve of what we did in the configureForIntroAnimation. Then when we commit the transaction, everything moves into the center.

Basic animations (16:15)

Let’s move on to CAAnimation. Let’s talk a little bit about the picture frame. Notice that it moves, but it also has this fun little wobble to it. We want to pretend like we have some motion or we actually have some physics behind it.

lazy var pictureFrameAnimation: CAAnimation = {
	let pictureAnimation = CAKeyframeAnimation()
	pictureAnimation.beginTime = CACurrentMediaTime() + 0.25
	pictureAnimation.keyPath = "transform.rotation"
	pictureAnimation.values = [0, -30.degreesToRadians, -10.degreesToRadians,
-20.degreesToRadians]
	pictureAnimation.duration = 0.75
	pictureAnimation.keyTimes = [0, 0.33, 0.66, 1]
	pictureAnimation.timingFunctions = [
		CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut),
		CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut),
		CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn),
	]
	return pictureAnimation
}()

By doing that, we can create a CAAnimation class. In this case, it’s a lazy variable used to initialize it once, but we’re going to use CAKeyframeAnimations. Keyframe animations give you the ability to set multiple states in a row.

In this case, we want that wobble. That means we’re going to animate the transform.rotation, which means you’re animating the way that this thing rotates along the Y axis. If you look, there is a series of values for your keyframe animation. In this case, zero, negative 30, negative 10, and negative 20. Each one of those is an angular value for how I want it to move. We have a duration for the entire thing.

We have keyTimes, which allows you to specify how long different parts of each one of your keyframe animations is going to take. Notice we have four values, four keyTimes, and the range for keyTimes is between zero to one. Each one of these is going to take about a third of our total duration, which is, in this case, about three-fourths of a second. Also, note that you can do timing functions for each part of your keyframe animation. In this case, we’re going to do an ease in, ease out between the first and the second, ease out between the second, and the third, and so on.

One of the other really fun, handy things is that there is a beginTime property on your basic animations. This allows you to adjust the offset of when your animation is going to start. By using CACurrentMediaTime(), you can add to that and say essentially, “This is going to start a quarter of a second after the current time.”

We have several things happening in our animated sequence. We want our picture frame to move laterally first, and then start swinging. To do that, one way we can do it is by delaying the basic animation for our rotation about a quarter of the second. When we put this all together, we get both lateral movement and rotation.

illustrationLayer.leftArm.addAnimation(
	leftArmAnimation, forKey: "leftArmIntroAnimation")
illustrationLayer.rightArm.addAnimation(
	rightArmAnimation, forKey: "rightArmIntroAnimation")
illustrationLayer.pictureFrame.addAnimation(
	pictureFrameAnimation, forKey: "pictureFrameIntroAnimation")

One thing you have to do is add animations to elements. In this case, let’s talk a little bit about the left arm, the right arm, and the picture frame.

You’ll notice that all of them have a little bit of rotation on them, The arms will pop up and have a little bit of a bounce on them and the picture frame moves as well. To do that, we’re going to call addAnimation. We have a property called leftIntroAnimation that we’ll go into in just a bit, and we give it forKey, which is not required, but I do tend to recommend it. It’s a way that you can reference an animation later on. It also helps in debugging because you can print out what animation you might be looking at by name.

static func armAnimation() -> CAKeyframeAnimation {
	let armAnimation = CAKeyframeAnimation()
	armAnimation.beginTime = CACurrentMediaTime() + 0.25
	armAnimation.keyPath = "transform.rotation" // Note we animate rotation
	armAnimation.duration = 0.4
	armAnimation.timingFunctions = [
		CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut),
		CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn),
	]
	return armAnimation
}

So for our arm animations, we are going to use some of our same tools. We’re going to use CAKeyframeAnimation, using rotation to make it do that little bounce. We’re also going to do the same begin time. We’re going to do a little bit shorter duration, some of our timing functions, but in this case, for our arm animations, we’re going to have a base function that gives us back a standard arm animation. But we need to do it a little bit differently for our left and our right arms.

static func armAnimation() -> CAKeyframeAnimation {
	...
}

lazy var leftArmAnimation: CAAnimation = {
	let leftAnimation = IntroView.armAnimation()
	leftAnimation.values = [0,
		47.degreesToRadians,
		IntroView.onboardingIntroArmAngle]
	leftAnimation.keyTimes = [0, 0.5, 1]
	return leftAnimation
}()

Our left arm animation is going to call into this base implementation and set a few additional properties on it because the left arm and the right arm have different angles because they’re on different sides. We’re going to construct that basic keyframe animation for our arms, set the values, set the keyTimes, and return it.

We could also have put the keyTimes in our constructor method, but in case the left arm or the right arm needed different values or different number values, that seemed a little bit risky. You always want your values and your keyTimes to be the same. Now we’ve got our bouncing little arms. We’ve got the picture frame rotating, and we’ve got everyone coming in from the left and the right.

Animation groups (21:39)

Let’s talk about CAAnimationGroup. When the arms come in from the left and the bottom with a little bit of rotation, we can use a CATransaction, but we have a few new interesting things. There are some fireworks in the background, and those fireworks do a combination of things: they grow, fade in, and then they repeat for a long period. So for this, we’re going to need to try animation groups.

static func identityTransformAnimation() -> CAAnimation { ... }
static func fadeAnimation() -> CAAnimation { ... }
static func largeFireworkAnimationGroup() -> CAAnimationGroup {
	let animationGroup = CAAnimationGroup()
	animationGroup.repeatCount = HUGE
	animationGroup.animations = [identityTransformAnimation(), fadeAnimation()]
	return animationGroup
}

func configureFireworkAnimation() {
	let fireworkAnimationGroup = IntroView.largeFireworkAnimationGroup()
	fireworkAnimationGroup.duration = 3.5
	illustrationLayer.topLeftFireworkMaskLayer.addAnimation(
		fireworkAnimationGroup, forKey: "fireworkGroupAnimation")
}

Animation groups allow you to apply multiple CAAnimation to a single object at the same time. In this case, we have an identityTransformationAnimation, a fadeAnimation, and we’re going to put those together under one thing called a largeFireworkAnimationGroup.

It’s not as complicated as it sounds. For the animation group, create a CAAnimationGroup. I’m going to put the repeat count at HUGE, which pretty much means indefinitely. Then I’m going to assign an array of animations to that group, which in this case is the identity and the fade.

Identity animation, in this case, is going to be that growth animation. It doesn’t seem like it’s a growth animation, but that’s because we’re going to use some of our tricks from before. What we’ll do is transform the fireworks to be very small. Then, we’re going to scale them down and animate them to their normal size.

Typically in graphics, identity operation is returning something back to its normal state, so we’re going to scale them down and then transform them up. Next, we’re going to fade our fireworks in. When we scale them down, we’re also going to put their alpha at a low or zero value. Then we’re going to fade it in.

Now when we are going to configure this, we create our animation group. You can set a duration for the entire group, so each animation in the group might have a different one. The fade and transform might be really quick, but the whole process is going to take about three and a half seconds. Then we’re going to repeat the whole animation group again. Then all we have to do is say addAnimation, just like our other basic animations.

An animation group can be applied, like a CAAnimation. What do we get out of that? Well, we get our nice, fun fireworks where they grow in size, they fade in or they fade out, depending upon how we have it set up. And it repeats.

CATransaction - Part 2 (24:41)

Let’s go back to CATransaction. CATransactions are a really powerful concept, and it’s really handy because they work similar to UIView animations. For this screen, we have many things happening, and they happen all in a row. Animation 1 happens, and when that’s done, we add animation 2, etc.

CATransaction.begin()
CATransaction.setAnimationDuration(0.45)
CATransaction.setAnimationTimingFunction(
	CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn))
...

CATransactions have a completionBlock. To do that, you can start your transaction, set your properties that you normally do like last time, duration, timing function, but then you can also set a completion block.

// After moving the main items into the center,
// animate the arms and legs back to their identity state
CATransaction.setCompletionBlock { [unowned self] in
	CATransaction.begin()
	CATransaction.setAnimationDuration(0.2)
	CATransaction.setAnimationTimingFunction
		(CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut))

	self.illustrationLayer.leftArm.transform = CATransform3DIdentity
	self.illustrationLayer.rightArm.transform = CATransform3DIdentity
	self.illustrationLayer.leftLeg.transform = CATransform3DIdentity

	CATransaction.commit()
}

This completion block is going to happen when the transaction is completed.

...

// Move the character to center
illustrationLayer.femaleBody.updatePosition(
	CGPointMake(introAnimationOffset, 0))
illustrationLayer.femaleHairBackground.updatePosition(
	CGPointMake(introAnimationOffset, 0))

// Scale trees back up
illustrationLayer.trees.transform = CATransform3DIdentity

CATransaction.commit()

At the end of our duration of 0.45, so about half a second again, this CATransaction will happen, and you can chain these together. You want to be careful so that you don’t have this chain of doom that goes 20 tabs out to the right.

In this case, we want to move the main items into the center like we did with our first intro screen. Everything starts to the left or the right. We move it to the center, but if we notice the way this works is that after the character gets to the center, you’ll see the arms and legs do an animation at that point. We want to trigger that only after our first transaction is complete. After we move our character to the center, we’re going to put an identity transform onto the left arm, the right arm, and the left leg so that it undoes the initial rotation that we put on them.

Next, commit that second transaction within the block. After this completion block, we still have to do our first transaction. One of the main things you want to consider is that you need to set your completion block before you commit your transaction because you can’t put a completion block after something’s already started. It can get a little counter-intuitive because it’s going to look reverse oriented. You’re going to have your last operation inside of your second to last, inside your first one. It’s going to work similarly to our first screen where we’re going to take our female character and move her to the center.

We’re also going to do that with the trees, where we just transform them into their identity state. We’re going to unscale them. When we’re done we have our character move to the center. After that’s done, we had the arms and leg do their animation, and we have the trees and the buildings scale upwards back to their original state.

Bonus: CATransitions! (28:03)

One last thing, as a bonus, is we have CATransitions. You’ll notice in a number of these animations that the text transitions between states is in a little bit more of an elegant fashion than our normal set text. Set text is very effective, but when you replace the text within about one frame, it’s very jarring. When you’re fading colors around your text, we really want to consider fading the text as well.

extension CATransition {
	static func simpleFade() -> CATransition {
		let fadeTransition = CATransition()
		fadeTransition.duration = 0.125
		fadeTransition.type = kCATransitionFade
		fadeTransition.timingFunction = CAMediaTimingFunction(
			name: kCAMediaTimingFunctionEaseInEaseOut)
		return fadeTransition
	}
}

What we have right here is a simple category or extension on a CATransaction for a factory method. This one creates a simple fade transition. We can apply this to our UI label. This looks very similar to our basic animations. We have a duration, we have a timing function, but we also have a type. In this case, we’re going to call it a fade type. And that’s all there is to it. I just return this simple fade.

let simpleFadeTransition = CATransition.simpleFade()
titleLabel.layer.addAnimation(simpleFadeTransition,
	forKey: "changeTextTransition")

The way that we use this is we get our simpleFadeTransition and then add the animation to our title label before we set our text. As long as you add this and then set text, you get that really nice transition effect.

Now if you’re curious, and you want to play with this in the Fitbit app, it’s the onboarding experience. You can also get it by going to the details for your hourly activity for that day. There’s a little “Learn More” button in the top-right that’ll present this at another time.

Conclusion (30:02)

Wrapping up, I’d like to encourage people to use the asset pipeline that works best for what you’re building. In our case, Sketch to PaintCode into code worked fairly well. However, in retrospective, there were a few more tools that I should have checked out such as QuartzCode.

One of the other things is that after you get your assets into a state, that’s equilibrium, doing animations is really quick and simple, and it’s a lot of fun. You can combine core animations, animation groups, and transactions to produce a swath of different experiences. Lastly, transitions are both simple and effective. I really encourage you to consider them for a variety of different UI elements, one of which is just tech labels.

Q & A (31:02)

Q: With all the timing, and ease in, ease out combinations, there’s a lot of build and run. Do you have any tips for how you can converge on 0.45 without having to go through a million iterations?

Stephen: There are a couple of things you could do. In our case, we had a designer who’s very capable with motion and animation. They mocked everything up in After Effects and we had a movie to go from, which was immensely helpful because it gives you something to go for as opposed to just shooting in the dark. One of the other things is that a number of the prototyping tools the designer might use will have similar types of curves and values that they might be using. So you can get those values from them to start with. But at the end of the day, both the values that they mocked up and the values that you use are going to need tweaking. It’s really simple, but just communicating with your designer in person or over a video chat or something and doing a couple of runs, “What do you think of this? Can you tweak it a little bit?”. Some of the other tools like QuartzCode and Squall might help with that as well. QuartzCode and Squall both directly implement animations in code, so you don’t have to do it yourself. One of the downsides of that is it’s a little less human readable, and the maintenance pattern is that you have to go back directly to the tool.

Q: What are some tips that you would recommend as far as working with motion designers, as opposed to just static designers?

Stephen: One thing you can do is set your own animation curves. You can define them based off of a Bezier path. Some programs, I believe After Effects, have the ability to actually export the curve that they’re using. So if the motion designer really, really wants a specific curve, you can set that curve, and you can build up a library of factory methods of different curve types that you can apply to your animation. You can also build up your own curve set. It’s not the most intuitive thing at all because it’s just defining a Bezier path, but if you have a designer with a toolset that is very comfortable with that, that’s definitely one option. I personally only go to curves and animations if it really makes sense because it keeps things a little simpler and more consistent, but at the end of the day, they typically do know best.

Q: Have you had any issues combining your animations with Auto Layout?

Stephen: Yes and no. CALayers don’t necessarily respond to Auto Layout. They’re hosted within a UIView, so your UIView responds to Auto Layout, but it hosts layers. In this case, because we had a central layer, we resized it. So you have to manually set the bounds or the frame of your layer based upon when the bounds or the frame of your host view changes, if your layer is not a host layer. Because of that, you’ll notice that many of the animations we used, for example, when the left and the right items were starting off the screen and moving to the center, those were based off of view.bounds and not Auto Layout. When you’re doing UIView animation, I highly recommend using Auto Layout inside of your animation block because that’s the recommended course. When you move to the CALayer world, you’re hosted within a view, which should have Auto Layout laid out, but then you’re using bounds and frame a bit more.


Stephen Barnes

Stephen Barnes

Stephen is an engineer, an artist, and many things in between. He is currently a Senior iOS Developer at Fitbit, but he has previously worked on a number of things such as 3D, real-time training simulations, babysitting apps, retail apps, 84" touchscreens Mac apps, and more. Along the way he has enjoyed writing shaders in GLSL for an OpenSceneGraph based graphics engine and building useful libraries such as RZTransitions for iOS view controller transitions. And best of all, he got to work with a whole lot of really smart people to teach him things along the way. When he isn’t working on mobile apps, he enjoys digital art, West Coast Swing dancing, and cheesy sci-fi books.