When do you use PhaseAnimator, Canvas and TimelineView?
Predict first
Thousands of animated particles must be drawn at 60 fps. Which is the better fit?
// A: one View per particle
// B: a Canvas inside a TimelineView(.animation)
// C: a List of particles
- A
- B
- C
Mental model. Views are Lego bricks, each one a separate object. Canvas is painting straight onto the board: much faster for thousands of dots, but the board does not know about the individual dots.
PhaseAnimator cycles a view through a list of phases (any Sequence of values), applying a content closure per phase and an animation closure for each transition between them. It suits looping or triggered effects (a pulse, a “shake then settle”) without a state machine; pass a trigger to run it once per change instead of continuously. For richer timing across several properties, keyframeAnimator lets you define tracks of keyframes (linear, spring, cubic) for a value struct. Both run in SwiftUI’s animation system, so they honour Reduce Motion only if you handle it.
Canvas is an immediate-mode drawing surface: a closure receives a GraphicsContext and size and draws paths, images, text and resolved symbols directly, with no per-item view or identity. That makes it far cheaper than thousands of views for particles, charts, grids and games, at the cost of no accessibility and no built-in interaction (you add gestures, and accessibility labels on the container, yourself). TimelineView re-evaluates its content on a schedule (every frame with .animation, periodic, or at specific dates) and passes the current date, which is the way to draw things that change with time: clocks, countdowns, or a Canvas animated by the date. Combined as TimelineView(.animation) { ctx in Canvas { … } }, they form SwiftUI’s particle and custom-animation engine. Pick views for most UI, Canvas when the count of items makes views too heavy, and TimelineView when time itself is the input.
The canvas draws straight into one surface without a view and identity per particle, and the timeline calls it every frame.
Common traps
- Using
Canvasfor ordinary UI. It loses accessibility, hit testing and layout; use views. - A
TimelineView(.animation)that does heavy work each frame. It runs at display rate; keep the closure light.
They might follow up with
- How do you make a
Canvasaccessible? Put accessibility labels and elements on the container, since its contents are not views. - When do you reach for
keyframeAnimator? When several properties need independent timing in one effect.
Related questions
- 8.7.1 What is the difference between implicit and explicit animation, and how do transitions fit in?
- 8.2.1 How does SwiftUI decide to re-run body, and how do you keep updates cheap?
- 6.3.1 What is a run loop, and what happens in one iteration?
← What is the difference between implicit and explicit animation, and…