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 get3DTransformWithX:0 andY:0.2];
}
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”.
- (void) get3DTransformWithX:(float)x andY:(float)y {
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.