How do implicit and explicit animations differ in Core Animation, and how does CATransaction fit in?

9.4.1 · L3 · Interview

Predict first

What does a fresh CABasicAnimation look like, and what is the default implicit duration?

import UIKit

let a = CABasicAnimation(keyPath: "opacity")
print(a.isRemovedOnCompletion, a.fillMode == .removed, a.fromValue == nil, a.timingFunction == nil, a.autoreverses, a.repeatCount)
print(CATransaction.animationDuration())
  • true true true true false 0.0, then 0.25
  • false true true true false 0.0, then 0.25
  • true true true true false 0.0, then 1.0

Predict first

What does UIView.animate do to the model value, and what is attached to the layer?

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)
UIView.animate(withDuration: 1) { v.frame.origin.x = 100 }
print(v.frame.origin.x, v.layer.animationKeys() ?? [])
  • 0.0 []
  • 100.0 ["position"]
  • 100.0 []

Mental model. Implicit animation is a lift that glides on its own when you press a floor. Explicit animation is you scripting the lift’s route, floor by floor, and the “floor” indicator (the model) stays put until you change it.

An implicit animation happens automatically: change an animatable property of a standalone CALayer and Core Animation animates from the old to the new value for 0.25 seconds (CATransaction.animationDuration()), with no animation code, because the layer asks its “action” for the property and gets a default CABasicAnimation. The backing layer of a UIView opts out: the view, as the layer’s delegate, returns “no action” outside an animation block, which is why a plain property change on a view jumps. UIView.animate turns the actions back on for the changes inside its block.

An explicit animation is one you build and add: CABasicAnimation(keyPath:) for from–to values, CAKeyframeAnimation for a path or a series of values, CASpringAnimation for springs, CAAnimationGroup to combine them, and CATransition for transitions. A new basic animation has fromValue nil (animate from the current presentation value), no timing function, does not auto-reverse, repeats zero times, and has isRemovedOnCompletion true. You add it with a key (layer.add(anim, forKey:)), and the key lets you replace or remove it. The model value is not changed by adding it, so the usual recipe is to set the model to the end value and add the animation from the old value. CATransaction groups changes: begin() and commit() bracket a batch, setAnimationDuration sets the duration for implicit animations inside, setDisableActions(true) makes changes happen without implicit animation (the way to move a layer instantly), and setCompletionBlock runs when all the animations in the batch finish. Remember that a completion block means “the animations finished”, not “the value was set”.

A new animation removes itself when done, starts from the current value, has no custom timing and does not repeat. The default implicit duration is a quarter of a second.

The model changes to its end value immediately, and UIKit adds a Core Animation animation for the position to the layer.

Common traps

  • Expecting a plain property change on a UIView to animate. The view’s own layer has implicit animation switched off outside UIView.animate.
  • Adding an explicit animation and not updating the model. The layer snaps back when the animation ends.

They might follow up with

  • How do you move a layer without animation? Wrap the change in a CATransaction with setDisableActions(true).
  • What is the forKey for? It names the animation so a later add replaces it and you can remove it by name.

Related questions

  • 9.2.2 What are the model, presentation and render layer trees, and how do the layer geometry properties relate?
  • 9.4.2 How do UIView.animate, UIViewPropertyAnimator and SwiftUI’s animations relate, and when do you pick each?
  • 8.7.1 What is the difference between implicit and explicit animation, and how do transitions fit in?

How do UIView.animate, UIViewPropertyAnimator and SwiftUI’s… →