SwiftUI Animation (iOS 26+)
Review, write, and fix SwiftUI animations. Apply modern animation APIs with correct timing, transitions, and accessibility handling using Swift 6.3 patterns.
Contents
- Triage Workflow
- withAnimation (Explicit Animation)
- Implicit Animation
- Spring Type (iOS 17+)
- PhaseAnimator (iOS 17+)
- KeyframeAnimator (iOS 17+)
@Animatable Macro- matchedGeometryEffect (iOS 14+)
- Navigation Zoom Transition (iOS 18+)
- Transitions (iOS 17+)
- ContentTransition (iOS 16+)
- Symbol Effects (iOS 17+)
- Symbol Rendering Modes
- Common Mistakes
- Review Checklist
- References
Triage Workflow
Step 1: Identify the animation category
Step 2: Choose the animation curve
Use the advanced catalog [blocked] when presets do not express the intended motion.
Step 3: Apply and verify
- Confirm animation triggers on the correct state change.
- Test with Accessibility > Reduce Motion enabled.
- Verify no expensive work runs inside animation content closures.
- For CA bridges, use Coordinators for delegates, invalidate display links, treat frame-rate ranges as hints, and adapt work to the actual refresh rate.
withAnimation (Explicit Animation)
Implicit Animation
Use withAnimation for state-mutation ownership, .animation(_:body:) for
selected modifiers, and .animation(_:value:) for simple value-bound changes.
Spring Type (iOS 17+)
Prefer the perceptual form or a preset. Load the advanced reference only when physical, response-based, or settling parameters are required.
PhaseAnimator (iOS 17+)
Cycle through discrete phases with per-phase animation curves.
Trigger-based variant advances to the next phase on each trigger change:
KeyframeAnimator (iOS 17+)
Animate multiple properties along independent timelines.
Keyframe types: LinearKeyframe (linear), CubicKeyframe (smooth curve),
SpringKeyframe (spring physics), MoveKeyframe (instant jump).
Use repeating: true for looping keyframe animations.
Swift 6: keyframe closures are @Sendable; capture state/env values before the modifier.
@Animatable Macro
Replaces manual AnimatableData boilerplate. Attach to any type with
animatable stored properties.
Rules:
- Stored properties must conform to
VectorArithmetic. - Use
@AnimatableIgnoredto exclude non-animatable properties. - Computed properties are never included.
matchedGeometryEffect (iOS 14+)
Synchronize geometry between views for shared-element animations.
Exactly one source view per ID should be visible; otherwise results are undefined.
Navigation Zoom Transition (iOS 18+)
Pair matchedTransitionSource on the source view with
.navigationTransition(.zoom(...)) on the destination.
Apply .navigationTransition on the destination view, not on inner containers.
Transitions (iOS 17+)
Control how views animate on insertion and removal.
See All Transition Types [blocked]
for the built-in catalog and custom Transition examples.
Asymmetric transitions:
ContentTransition (iOS 16+)
Animate in-place content changes without insertion/removal.
Types: .identity, .interpolate, .opacity,
.numericText(countsDown:), .numericText(value:), .symbolEffect.
Symbol Effects (iOS 17+)
Animate SF Symbols with semantic effects. .bounce, .pulse, .variableColor,
.scale, .appear, .disappear, and .replace are iOS 17+; .breathe,
.rotate, and .wiggle require iOS 18+.
Scope: .byLayer, .wholeSymbol. Direction varies per effect.
Symbol Rendering Modes
Choose .monochrome, .hierarchical, .multicolor, or .palette with
.symbolRenderingMode(_:); use .foregroundStyle to supply palette colors.
Variable symbols: use Image(systemName:variableValue:) (iOS 16+) for percentage fill. Use .symbolVariableValueMode(_:) (iOS 26+) to choose .draw or .color.
Docs: SymbolRenderingMode · symbolRenderingMode(_:) · Image(systemName:variableValue:) · symbolVariableValueMode(_:)
Common Mistakes
1. Using bare .animation(_:) when you need precise scope
2. Expensive work or actor-isolated reads inside animation closures
keyframeAnimator / PhaseAnimator content closures run every frame. Precompute expensive values, animate only visual properties, and capture state/env values before @Sendable keyframe closures.
3. Missing reduce motion support
For symbols, remove inherited effects; gate larger motion with reduceMotion ? .none : animation.
4. Multiple matchedGeometryEffect sources
Only one source view per ID should be visible at a time. Multiple visible sources with the same ID cause undefined layout.
5. Using DispatchQueue or UIView.animate
6. Forgetting animation on ContentTransition
7. navigationTransition on wrong view
Apply .navigationTransition(.zoom(sourceID:in:)) on the outermost destination view, not inside a container.
Review Checklist
- Animation curve matches intent (spring for natural, ease for mechanical)
-
withAnimationwraps the state change; implicit animation uses.animation(_:body:)for selective modifier scope or.animation(_:value:)with an explicit value -
matchedGeometryEffecthas exactly one source per ID; zoom uses matchingid/namespace -
@Animatablemacro used when synthesis fits; manualanimatableDatakept only when custom packing is clearer -
accessibilityReduceMotionchecked; noDispatchQueue/UIView.animate - Transitions use
.transition();contentTransitionis paired with animation and uses the narrowest implicit animation scope that fits - Animated state changes on @MainActor; animation-driving types are Sendable
References
- See references/animation-advanced.md [blocked] for CustomAnimation protocol, Spring variants, Transition types, symbol effects, Transaction system, UnitCurve, and performance guidance; Core Animation bridging patterns: references/core-animation-bridge.md [blocked].


