SwiftUI has offered phase and keyframe animation since iOS 17. Both describe motion richer than a single interpolated state change, but neither belongs in every interaction. Use a simple decision order: can ordinary state animation explain the change? If the motion has a few discrete, meaningful stages, consider PhaseAnimator. If several numeric properties require independently timed tracks, use KeyframeAnimator.

The smallest adequate abstraction is easier to interrupt, test, and adapt for accessibility. A complex timeline can produce an attractive demo while leaving basic questions—what happens after a second tap, or when the view disappears—unanswered.

Keep state animation as the default

A selected button, expanded panel, or changed value usually has two meaningful states. Bind appearance to the product state and use .animation(_:value:) or withAnimation for the transition. Do not duplicate one Boolean into several animation phases merely to use a more specialized API. Cancellation, reversal, and rapid input become harder when the animation model has more states than the feature.

Animated values present application state; they must not become that state. Whether a network operation completed or a form is valid cannot depend on reaching a frame. The feature must remain correct if animation is disabled or Reduce Motion is enabled.

Use PhaseAnimator for named beats

A phase animation suits a small sequence such as emphasize, hold, and settle. Each phase defines appearance, and SwiftUI interpolates between those appearances:

enum SavePhase: CaseIterable {
    case idle, lift, settle
}

struct SaveBadge: View {
    let trigger: Int

    var body: some View {
        Image(systemName: "checkmark.circle.fill")
            .phaseAnimator(SavePhase.allCases, trigger: trigger) { view, phase in
                view
                    .scaleEffect(phase == .lift ? 1.18 : 1)
                    .opacity(phase == .idle ? 0.8 : 1)
            } animation: { phase in
                phase == .lift ? .spring(duration: 0.22) : .easeOut(duration: 0.16)
            }
    }
}

Name phases after intent, not step1 and step2. A team can discuss whether settle is necessary, and tests can reason about the trigger and final state. If stages have no semantic distinction and exist only to copy individual frames from a video, a keyframe timeline or a purpose-built media asset may be a better model.

Choose a trigger with clear identity. An incrementing event token can replay a confirmation even when the underlying “saved” value remains true. Decide what repeated triggers do while a sequence is active. Restarting, ignoring, and coalescing are all plausible policies, but accidental interruption behavior is not a policy.

Use KeyframeAnimator for coordinated tracks

Keyframes fit short sequences where position, scale, rotation, and opacity follow different rhythms. A favorite icon might grow first, rotate slightly, and then settle. Define a small animation-value structure and let each track modify one property. Return every property to a deliberate stable value at the end so the next trigger does not inherit an accidental remainder.

More keyframes create more maintenance. Keep durations, curves, and track relationships near one another rather than scattering magic values through view modifiers. If the sequence has to pause, scrub, persist across navigation, or synchronize with audio, a simple trigger-driven keyframe modifier may no longer be the right ownership model.

Animation also interacts with layout identity. In a list, a stable item identity helps SwiftUI understand that a view changed rather than being removed and reinserted. Fix identity and state ownership before compensating for visual discontinuities with a longer timeline.

Reduce Motion is an alternate design

Read accessibilityReduceMotion and replace unnecessary movement, scaling, and rotation with a fade or immediate update. The alternative must still communicate success, error, and hierarchy. Setting every duration to zero can rapidly flash through several phases, preserving neither meaning nor comfort.

Stop decorative loops when they are not visible and when the scene backgrounds. Motion must not be the sole information channel. A save confirmation should also update a label, symbol, or accessibility announcement. A drop target needs a shape or text change in addition to a bounce. Color alone is not enough either.

Measure performance and test boundaries

Transforms and opacity are often a better starting point than continuously animating complex layout, large shadows, blur, or broad translucent materials. Measure frame pacing and energy on supported devices with Instruments. A smooth simulator is not a performance result. In lists, isolate animation state so one local event does not invalidate every row.

Unit tests can cover the condition that produces a trigger, the state reducer, the repeated-input policy, and the Reduce Motion branch without sleeping for a real animation. UI tests should confirm that important feedback appears, repeated input follows the policy, and the view returns to an interactive final state. Reserve a small amount of visual review for the quality of a curve; business correctness should not depend on pixel timing.

Ordinary animation handles one state transition. Phase animation handles a handful of named beats. Keyframes coordinate precisely timed properties. Pick the smallest model, then design interruption, replay, accessibility, lifecycle, and the final state. Good motion explains what the interface did. If turning off motion also removes the feature’s meaning, the animation owns responsibility that belongs elsewhere.