SwiftUI Field Guide

Phase Animations

Phase animations allow you to animate through multiple phases. And under the hood, it just uses regular animations and their completion handlers. So here's a wiggle example where we wiggle a view twice when clicked. We can also build this wiggle with keyframe animations or an Animatable modifier.

Code
struct Wiggle: View {    @State private var trigger = 0        let angles: [Double] = [ 0, -15, 12, -8, 4 ]        var body: some View {        RoundedRectangle(cornerRadius: 12)            .fill(.red)            .frame(width: 80, height: 96)            .onTapGesture {                trigger += 1            }            .phaseAnimator(Array(angles.indices),                           trigger: trigger) { button, phase in                button                    .rotationEffect(.degrees(                                        angles[phase]                                    ))            } animation: { phase in                .easeInOut(                    duration: phase < 2 ? 0.1 : 0.15                )            }    }}
Preview
Loading preview…
Track
Phase angle over time-3003000.3250.65Time (s)

Drag points vertically to change values. Up and Down arrow keys edit a selected value; hold Shift for larger steps.

6 points.
0.00 s

Gotchas

Here's another example where we try to move up, wait, and move down. And we try to do the waiting by inserting the same phase twice. This does not animate as expected. And the reason is that when we animate from -100 to -100, the animation completes immediately because there is nothing to animate and then continues with the next animation (back to the starting value). If we want to add a delay, we need to modify the animation curve itself. We can use the delay toggle to see that effect.

Code
struct MoveAndWait: View {    @State private var trigger = 0        var body: some View {        RoundedRectangle(cornerRadius: 8)            .fill(.red)            .frame(width: 48, height: 48)            .onTapGesture {                trigger += 1            }            .phaseAnimator([ 0.0, -100, -100 ],                           trigger: trigger) { content, offset in                content                    .offset(y: offset)            } animation: { offset in                .easeInOut(duration: 0.5)            }    }}
Preview
Loading preview…
Curve
Interpolated vertical offset over time-100-50000.51Time (s)

Resources

Share Image

Phase Animator Behavior, on skipped phases and adding a delay.

https://chris.eidhof.nl/post/phase-animator/
Share Image

Four Ways to Shake compares phase animations, keyframes, and their timing curves.

https://chris.eidhof.nl/post/four-ways-to-shake/
Share Image

Swift Talk #402, Wobble Animation: a wiggle effect using a phase animator and the Animatable API.

https://talk.objc.io/episodes/S01E402-wobble-animation
Share Image

Apple's Wind your way through advanced animations in SwiftUI (WWDC23). Animation phases start at 2:23.

https://developer.apple.com/videos/play/wwdc2023/10157/?time=143
Share Image

Thinking in SwiftUI, Animations chapter: “Phase-Based Animations.”

https://www.objc.io/books/thinking-in-swiftui/
Share Image

Natalia Panferova's Creating multi-step animations with PhaseAnimator in SwiftUI goes into way more detail on the full API of phase-based animations.

https://nilcoalescing.com/blog/PhaseAnimationsInSwiftUI/