What are the model, presentation and render layer trees, and how do the layer geometry properties relate?

9.2.2 · L5 · Interview

Predict first

What does the model value read after a fade animation is added?

import UIKit

let w = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
w.makeKeyAndVisible()
let v = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
w.addSubview(v)
v.layer.opacity = 1
let a = CABasicAnimation(keyPath: "opacity")
a.toValue = 0
a.duration = 1
v.layer.add(a, forKey: "fade")
print(v.layer.opacity, v.layer.animationKeys() ?? [])
  • 0.0 ["fade"]
  • 1.0 ["fade"]
  • 1.0 []

Predict first

What is the frame of a 100×100 layer at position (50, 50) with the default anchor, with anchor (0, 0), and then scaled 2×?

import UIKit

let sl = CALayer()
sl.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
sl.position = CGPoint(x: 50, y: 50)
print(sl.frame)
sl.anchorPoint = CGPoint(x: 0, y: 0)
print(sl.frame)
sl.transform = CATransform3DMakeScale(2, 2, 1)
print(sl.frame)
  • (0,0,100,100), then (50,50,100,100), then (50,50,200,200)
  • (50,50,100,100), then (50,50,100,100), then (50,50,200,200)
  • (0,0,100,100), then (0,0,100,100), then (0,0,200,200)

Predict first

What does presentation() return for a layer that was never added to a window?

import UIKit

print(CALayer().presentation() == nil)
  • true
  • false

Mental model. The model layer is the script (“the actor ends up stage left”); the presentation layer is where the actor is standing right now during the walk; the render tree is the photograph taken from the audience.

Core Animation keeps three trees. The model tree is the layers you create and configure; reading layer.opacity gives the target value as you last set it. The presentation tree (layer.presentation()) holds the in-flight values while an animation runs: it is a read-only copy whose properties are the interpolated, currently visible values, and it is nil for a layer that has not been committed. The render tree lives in the render server and is what actually gets drawn. So after layer.add(animation, forKey:) the model value has not changed: a fade to 0 added to a layer at opacity 1 still reads 1, and when the animation ends the layer snaps back to the model value unless you also set it (or keep it with fillMode and isRemovedOnCompletion, which is the wrong fix). Set the model value to the end state, then add the animation from the old value. Use the presentation layer when you need the on-screen position, for example for hit testing during an animation.

A layer’s geometry is defined by bounds (its own size and origin), position (where its anchor point sits in the parent) and anchorPoint (a unit-square point, default 0.5, 0.5, the centre, around which it rotates and scales). frame is derived: for a 100×100 layer at position (50, 50) it is (0, 0, 100, 100) with the default centre anchor, but (50, 50, 100, 100) with an anchor of (0, 0). A transform changes the frame (a 2× scale doubles it) but not the bounds. A layer’s backing store is its bitmap (contents), created by draw(_:) or set from an image; memory cost is the pixel size (bounds × scale squared × 4 bytes), so a full-screen, retina layer with its own bitmap costs several megabytes, and many such layers add up.

The animation is attached, but the model value is unchanged, so it still reads 1.0.

The frame is derived from bounds, position and anchor point, so moving the anchor moves the frame, and a scale transform enlarges it from the anchor.

The presentation tree exists only for committed layers, so a free-standing layer has none.

Common traps

  • Adding an animation and not setting the model value. When it finishes the layer jumps back to the old value.
  • Using fillMode = .forwards with isRemovedOnCompletion = false to “keep” the end state. The model still says the old value; set the model instead.

They might follow up with

  • How do you read the on-screen position during an animation? From layer.presentation()?.position.
  • Why does a rotation seem to pivot around a corner? The anchor point decides the pivot; change it and the position together.

Related questions

  • 9.2.1 Walk through how a frame gets from your code to the screen.
  • 9.4.1 How do implicit and explicit animations differ in Core Animation, and how does CATransaction fit in?
  • 7.2.1 What is the relationship between UIView and CALayer?

← Walk through how a frame gets from your code to the screen.