Core Animation: 3D Perspective
Last time, we looked at how Core Animation lets you perform simple animations, including animating along paths.
Now, using Core Animation, let’s look at a way to simulate a 3D perspective. Core Animation is not ideal for 3D graphics—use OpenGL for that—but it’s handy to be able to use 3D perspective at times for simpler things.
For this example, we’ll display a two-room house with a front door.

The house’s seven walls fall neatly on a grid. Walls 1 through 4 run east-west, while 5 through 7 run north-south. Each wall is a layer. All walls are sublayers of the floorLayer, which is in turn a sublayer of the rootLayer.

Set up
-(void)setupHostView {
// Set up the root layer. Nothing unusual here.
rootLayer = [[CALayer layer] retain];
rootLayer.needsDisplayOnBoundsChange = YES;
rootLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
[roomView setLayer:rootLayer];
[roomView setWantsLayer:YES];
// Set up the floor layer. Nothing unusual here.
floorLayer = [[CALayer layer] retain];
floorLayer.delegate = roomView;
floorLayer.bounds = rootLayer.bounds;
floorLayer.frame = rootLayer.frame;
floorLayer.autoresizingMask = kCALayerHeightSizable | kCALayerWidthSizable;
floorLayer.fillMode = rootLayer.fillMode;
floorLayer.position = CGPointMake(400, 200);
[rootLayer addSublayer: floorLayer];
// Finally, apply the 3D perspective. Without this, things look quite odd.
floorLayer.sublayerTransform = [self get3DTransform];
}
Applying the 3D perspective relies on a poorly-documented feature of Core Animation’s CATransform3D structure, a 4-by-4 matrix used to perform matrix transformations. Apple’s documentation says that changes to CATransform3D.m34 “affect the sharpness of the transform.” For our purposes, this means “make things look 3D”.
- (CATransform3D) get3DTransform {
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -2000;
return transform;
}
Creating the walls
Creating the walls is relatively straightforward. Each wall has an origin and a size.
- (CALayer *)makeWallAtOrigin:(CGPoint)origin size:(CGSize)size color:(CGColorRef)color {
// Create the wall with the desired background color.
CALayer *wall = [CALayer layer];
wall.backgroundColor = color;
wall.anchorPoint = CGPointZero;
CGRect frame;
frame.origin = origin;
frame.size = size;
wall.frame = frame;
wall.bounds = frame;
return wall;
}
Walls 3, 4, and 6 lie further back and need to be translated along the z axis.
- (void)moveWall:(CALayer*)wall toDepth:(float)depth {
NSNumber* value = [NSNumber numberWithFloat:depth];
[wall setValue:value forKeyPath:@"transform.translation.z"];
}
North-south walls 5, 6, and 7 need to be rotated.
- (void)rotateWall:(CALayer*)wall withDegrees:(float)degrees {
// Rotation occurs relative to the layer's anchorPoint, which by
// default is in the middle of the layer. So unless you move the
// anchorPoint, rotation will cause the layer to pivot on its center.
// We want rotation to pivot on the wall's left point, so we'll set
// anchorPoint to 0.
wall.anchorPoint = CGPointZero;
float radians = DegreesToRadians(degrees);
NSNumber* value = [NSNumber numberWithFloat:radians];
[wall setValue:value forKeyPath:@"transform.rotation.y"];
}
Putting it all together
- (void)addWalls:(CALayer *)parentLayer {
float w = [self cellWidth];
float h = [self cellHeight];
float x = parentLayer.frame.size.width/2 - (w*5)/2;
float y = parentLayer.frame.size.height/2 - (h*3/2);
// Wall 1
CALayer* wall1 = [self makeWallAtOrigin:CGPointMake(x + w*1, y + h)
withSize:CGSizeMake(w*1, h) color:_lightGrayColor image:image1];
[parentLayer addSublayer:wall1];
// Wall 2
CALayer* wall2 = [self makeWallAtOrigin:CGPointMake(x + w*3, y + h)
withSize:CGSizeMake(w*1, h) color:_lightGrayColor image:image2];
[parentLayer addSublayer:wall2];
// Wall 3
CALayer* wall3 = [self makeWallAtOrigin:CGPointMake(x + w*1, y + h)
withSize:CGSizeMake(w*2, h) color:_darkGrayColor image:image3];
[self moveWall:wall3 toDepth:w*-6];
[parentLayer addSublayer:wall3];
// Wall 4
CALayer* wall4 = [self makeWallAtOrigin:CGPointMake(x + w*1, y + h)
withSize:CGSizeMake(w*3, h) color:_darkGrayColor image:image4];
[self moveWall:wall4 toDepth:w*-10];
[parentLayer addSublayer:wall4];
// Wall 5
CALayer* wall5 = [self makeWallAtOrigin:CGPointMake(x + w*1, y + h)
withSize:CGSizeMake(w*6, h) color:_lightGrayColor image:image5];
[self rotateWall:wall5 withDegrees:90];
[parentLayer addSublayer:wall5];
// Wall 6
CALayer* wall6 = [self makeWallAtOrigin:CGPointMake(x + w*1, y + h)
withSize:CGSizeMake(w*4, h) color:_orangeColor image:image6];
[self moveWall:wall6 toDepth:(w*-6 + 1)];
[self rotateWall:wall6 withDegrees:90];
[parentLayer addSublayer:wall6];
// Wall 7
CALayer* wall7 = [self makeWallAtOrigin:CGPointMake(x + w*4, y + h)
withSize:CGSizeMake(w*10, h) color:_darkGrayColor image:image7];
[self rotateWall:wall7 withDegrees:90];
[parentLayer addSublayer:wall7];
}

Download and try it
Download (requires Leopard): application | source code
Thank you for the example.
The floorLayer is not rotated to x-z plan. I tried to rotate -90 degrees and then use transform3d on it without walls. But it’s not displayed perspectively. Could you please give me a hint?
Linan, the floorLayer won’t display with perspective because the 3D transform is being *applied* to the floorLayer. If you want to display the floorLayer with perspective, you’ll have to create a parent layer, add floorLayer to that parent layer, then apply the 3D perspective to that new parent layer.
Get it, thanks!
I feel in the animation function,
Instead of playing with zPosition
[floorLayer setValue:[NSNumber numberWith:xx] forKeyPath:@”sublayerTransformation.translation.z”]
is a better way.
I don’t understand why – (void) get3DTransformWithX:(float)x andY:(float)y is needed. You must have written this on the spot? It returns void when it should return a CATransform3D and your passing values in that are not been used.
Anyways, this actually help!!! Thank you for this. At first my rotations were not appearing 3D, and I was very disappointed, but the m34 translation was the issue! Thanks for posting the example.
Good catch, Brad. Yes, while writing the post I editted the code on the spot to fit the available width and I botched things. Now set right, thanks to you.
Hello,
I need to develop a grid with n X n dimensions. This grid will have n X n inner boxes in m X m outer boxes. Like, sudoku grid. I need to also give zooming animation when selected any box, all other boxes will be smaller, the selected main box will be zoomed. i did the coding using standard controls, inner boxes are uiimageview, outer boxes are uiview, but the navigation’s push animation is slow because loading 20 x 20 rectangles… also zooming is not perfect. it’s somewhat slower. I’ve did the zooming and grid using standard controls, and manually changed x,y co-ordinates and height and width for zooming, but the app is too slow. Could you suggest me right direction? Please provide example if possible..! Thanks.
Great tutorial. More informative and right to the point than some of those Core Animation books.
The only one thing I was always confused was the transform.m34 = 1.0 / -2000; part.
It took me quite a while to realize that it is essential to add 3D perspective to the sublayers.
Otherwise, regardless how you rotate or change zPosition, the transformed layers will never look like having depth (i.e. looking smaller when getting further).
Great ! I would love to have the source of an iPhone version of this app.
Thierry,
The core code would be the same, since Core Animation works on the iPhone.