Core Animation Recipes.

With this article, I’d like to share some bite-sized pieces of information I’ve learned while working on some of my latest projects. There’s also a sample project attached. Let’s start by introducing Core Animation.

Core Animation is responsible for almost everything you see on your iOS device’s screen. You’re probably familiar with UIKit, a lot of which is built on top of Core Animation: every UIView is backed by a CALayer. UIKit provides a high-level and powerful framework that can be used to do 80% of everything. If you’re reading this though, you probably think that’s not good enough.

With Core Animation, you can do everything UIKit does and much more. However, this comes at a price: you get a lower-level interface, which is harder to learn and to use effectively. In some circumstances you’ll need to know what the weak points of the hardware your app is running on are, just to get decent performance.

Core Animation is built on top of two even-lower-level frameworks: OpenGL and Quartz. Quartz is used for drawing/rendering 2D graphics. OpenGL takes care of compositing them, generating the scene that ultimately gets displayed - taking advantage of hardware acceleration.

So why write about it? The way I see it, Core Animation is one of the tools that allow you to take your app’s UX to the next level. It’s a key component in making apps beautiful, responsive, smooth, and realistic in the way they react to user input. Together, these attributes result in products that feel trustworthy, delightful and fun. And that’s exactly how they should feel.

This article is all about Core Animation, so I thought it would be nice to only use that API. As a result, there will be no drawing code - no -drawRect: at all.

The Players

CALayer

Core Animation’s workhorse! Think about it as a lower-level UIView. Can display all kinds of content. Supports things like drop shadows and rounded corners out of the box. Almost all of its properties can be animated.

As I mentioned earlier, every UIView has a CALayer (you can get to it using the layer property). Every layer can have any amount of sublayers, which are are very similar to subviews in UIKit.

CAShapeLayer

A subclass of CALayer, specialized in rendering shapes. Whatever shape you can draw with a CGPath, CAShapeLayer can display.

shapeLayer.path = [UIBezierPath bezierPathWithOvalInRect:shapeLayer.bounds].CGPath;

CAGradientLayer

Another subclass of CALayer that can display all kinds of axial gradients (a.k.a. linear gradients), with any number of stops and colors.

Masking

CALayers support masking: they have a mask property that you can set to another CALayer. The alpha channel of the mask layer will determine what portion of the layer will be displayed.

Note, from Apple’s docs:

The layer you assign to this property must not have a superlayer. If it does, the behavior is undefined.

In other words, a layer shouldn’t be used as a mask for two (or more) layers. It just doesn’t work. Clone the original mask layer every time you want to reuse it.

Drop Shadows

Drop shadows are very easy to implement.

A drop shadow example

Just set these properties on any CALayer:

Inner Shadows

Inner shadows are not very easy to implement.

An inner shadow example

I’ve searched for solutions to this for a while. Apparently, people on the interwebz are doing this by first creating a shape that is the negative of the shape you want to give a drop shadow to, then giving a drop shadow to this negative shape, and then masking the negative shape with the original shape, so that only its shadow remains. This works, but generating the negative shape is very hard. So I’ve developed my own solution - it’s similar but easier to implement, and it covers the vast majority of cases where you might want an inner shadow. It’s a three step process:

  1. Create a path by stroking the path of the shape you want to have an inner shadow, using CGPathCreateCopyByStrokingPath()
  2. Create a sublayer, set its shadow properties as desired, and set its shadowPath to the stroked path
  3. Mask the sublayer with the original shape

In other words, what we’re doing is making the contour of the shape drop a shadow, which of course will drop it inside and outside of the shape. But then we mask away the part of the shadow we don’t need, which is very easy as the mask coincides with the shape.

This said, I would be way happier if Core Animation supported inner shadows out of the box, like it does for drop shadows.

You can see this in action in the first screen of the attached example project, where you can also move your finger around to manipulate the shadowOffset property of some layers. Check out GBShadowsView.m in the sample project for the full code.

Rotation

Some people have trouble implementing infinite rotation, which you might use for e.g. a custom spinner while waiting on network activity. Or to create an (astronomically inaccurate) space scene.

A space scene built with Core Animation

It’s actually quite simple.

CABasicAnimation* earthRotationAnimation;
earthRotationAnimation = [CABasicAnimation
    animationWithKeyPath:@"transform.rotation.z"];
earthRotationAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2.0];
earthRotationAnimation.duration = 10;
earthRotationAnimation.repeatCount = INFINITY;
[self.earthImageLayer addAnimation:earthRotationAnimation
                            forKey:@"rotationAnimation"];

If your object isn’t rotating around its center, just create an empty layer or view where you want your object to be, and add your object in the middle of it. Core Animation is smart enough, so the performance hit when adding empty layers or views is negligible if not null.

In the sample app, the starry background is generated using a CAEmitterLayer, which is at the core of Core Animation’s particle system. You can create a lot of nice effects with particles, so check the code in GBRotationView.m if you’re interested.

Gradients

A CAGradientLayer has two interesting properties, both animatable:

A barcode scanner built with Core Animation

To create the animated “barcode scanner” thingy in the picture, I set my CAGradientLayer’s colors to [transparent, red, transparent] and then I animated the locations back and forth between [0.3, 0.35, 0.4] and [0.6, 0.65, 0.7], creating the illusion of a red laser moving up and down.

Doing this with a gradient is a terrible idea, but this might give you some insight into what can be accomplished with gradients alone. Can’t wait to see what funky animated psychedelic patterns you can come up with :)

Fun with Masking

Did you know that if you give a shadow to a layer you’ll use as a mask, the shadow will be part of the mask? And that everything will still be animatable?

A flashlight scene built with Core Animation

What if we used this to create a cool, animated “flashlight effect”? Check out the sample project to see how this is done - it’s very simple.

A Note About Performance

We used a bunch of shadows today. Keep in mind that computing the shadow from the layer’s alpha channel (which is the default behavior) is a very slow operation. You should always set shadowPath, so Core Animation can skip that operation - this has a dramatic effect on performance.

At the top of GBMaskingView.m there’s a SET_SHADOW_PATHS #define. Try setting that to NO, so you can see the huge performance hit with your own eyes.

I plan to write an entire article about performance on iOS, which will include a Core Animation section, so stay tuned.

Sample Project

Download the sample project, hack away, and have fun. Contact me if you have questions, and I’ll make sure to update this article.

Planet images included with the sample project courtesy of some guy.