Perspective in Core Animation

I fell in love with Core Animation right from the beginning. For a UI passionate developer like me Core Animation is just paradise. There are no limits to develop animated, easy and fluid user interfaces… And all that power with an extremely easy code to write.

However, nothing is perfect in this life and Core Animation in no exception. When you work with it you realize it was mostly thought for 2D interfaces and its behaviour is not that outstanding when you require 3D scenes. Even so, there’s a hint which will enable you to add some depth to the interface and greatly improve the results.

The key concept is CATransform3D. The CATransform3D is a transform matrix that lets us define a transforming action in 3D space (rotate, scale, offset, skew…) to a Core Animation layer. I’ll try to be practical not trying to explain the linear algebra behind these matrices. If you want tho go in-depth with this subject you can check out this article.

The one thing to know is that the transform matrix CATransform3D is applied using:

  • transform: apply the transform to the layer and its sublayers relative to the layer’s anchorPoint.
  • sublayerTransform: apply the matrix only to the layer’s sublayers, rather than to the layer itself.

The way to provide perspective is to directly access one of the matrix cells of the CATransform3D (m34) and modify its value using a parameter we’ll be calling newZPosition:

// provides a perspective transform
CATransform3D layerTransform = CATransform3DIdentity;
if (newZPosition != 0)
layerTransform.m34 = 1.0 / newZPosition;

// We then add the required rotation, in this case a 180° Y axis.
layerTransform = CATransform3DRotate(layerTransform, M_PI, 0.0f, 1.0f, 0.0f);

// Then we apply the transformation to the layer
CALayer *layer = … // get the layer to apply perspective;
containerLayer. transform = layerTransform;

The visual effects when changing this newZPosition can be seen in the attached project file and in the images below:

Nonetheless, most of the time we’ll be applying perspective to a complex layer hierarchy. To achieve this I usually create a container layer and a holder layer.

I apply the aforementioned transformation matrix to the container layer (modifying m34) using the method sublayerTransform so it applies to all its sublayers:

// provides a perspective transform
CATransform3D layerTransform = CATransform3DIdentity;
if (newZPosition != 0)
layerTransform.m34 = 1.0 / newZPosition;

// Get layer and apply transform
CALayer *containerLayer = [[[self layer]sublayers] objectAtIndex:0];
containerLayer.sublayerTransform = layerTransform;

while I apply the corresponding rotation, offset or scaling to the holder layer. This way, and making holder a root superlayer, I can create the necessary layer hierarchy preserving perspective for all of them:

// Get layers
CALayer *containerLayer = [[[self layer]sublayers] objectAtIndex:0];
CALayer *holder = [containerLayer valueForKey:@"__holderLayer"];

// Update xAngle Value
[containerLayer setValue:[NSNumber numberWithFloat:angleXRad] forKey:@"__angleX"];

// Apply rotations
CGFloat angleY = M_PI * 0.25;
CGFloat angleX = -M_PI * 0.25;
CATransform3D holderTransform = CATransform3DMakeRotation(angleX, 1.0f, 0.0f, 0.0f);
holderTransform = CATransform3DRotate(holderTransform, angleY, 0.0f, 1.0f, 0.0f);
holder.transform = holderTransform;

Sample code: the attached Xcode project will let you study the visual effects obtained when altering the relevant parameters.

Xcode Project    |   Sample Application