Animation Patterns
Correct API shapes and patterns for SwiftUI animations. Prevents the most common mistakes: mixed spring parameter generations, wrong PhaseAnimator/KeyframeAnimator closure signatures, and using matchedGeometryEffect where matchedTransitionSource is needed.
When This Skill Activates
Use this skill when the user:
- Asks to add, fix, or review animation code
- Mentions spring, bounce, or snappy animations
- Wants view transitions (insertion/removal, hero, zoom)
- Asks about PhaseAnimator or KeyframeAnimator
- Wants SF Symbol effects (bounce, pulse, wiggle, breathe)
- Mentions matchedGeometryEffect or matchedTransitionSource
- Asks about reduce motion / animation accessibility
- Wants to sequence or chain animations
- Mentions withAnimation, animation completions, or Transaction
- Wants scroll-driven effects (parallax, carousels,
scrollTransition, visualEffect)
- Mentions MeshGradient, TextRenderer, or Metal shader effects (
colorEffect, distortionEffect, layerEffect)
Decision Tree
Choose the right reference file based on what the user needs:
What are you animating?
│
├─ A state change (opacity, position, color)
│ └─ → core-animations.md
│ ├─ withAnimation { } — explicit animation
│ ├─ .animation(_:value:) — implicit animation
│ └─ Spring configuration — .spring, .bouncy, .snappy, .smooth
│
├─ A multi-step / sequenced animation
│ └─ → phase-keyframe-animators.md (PhaseAnimator)
│ └─ Cycles through discrete phases automatically or on trigger
│
├─ A complex multi-property animation (scale + rotation + offset)
│ └─ → phase-keyframe-animators.md (KeyframeAnimator)
│ └─ Timeline-based keyframes with per-property tracks
│
├─ A view appearing / disappearing
│ └─ → transitions.md
│ ├─ .transition() — insertion/removal
│ ├─ .contentTransition() — text/symbol changes
│ └─ .asymmetric() — different in/out
│
├─ A hero / zoom navigation transition
│ └─ → transitions.md (matchedTransitionSource section)
│ ├─ iOS 18+: matchedTransitionSource + .navigationTransition(.zoom)
│ ├─ UIKit: preferredTransition = .zoom (capture stable IDs, never views)
│ └─ iOS 14+: matchedGeometryEffect (NOT for NavigationStack)
│
├─ A UIKit view driven by SwiftUI state or gestures
│ └─ → transitions.md (Bridging UIKit and SwiftUI Animations)
│ └─ UIView.animate(.spring(...)) / context.animate in updateUIView
│
├─ An SF Symbol animation
│ └─ → symbol-effects.md
│ └─ .symbolEffect(.bounce), .pulse, .wiggle, .breathe, .rotate
│
├─ A scroll-driven, geometry-driven, or shader effect
│ └─ → visual-effects.md
│ ├─ .scrollTransition — carousel scale/parallax/caption fades
│ ├─ .visualEffect — position-based effects without GeometryReader
│ ├─ MeshGradient — animatable multi-point gradients
│ ├─ TextRenderer — per-line / per-glyph text animation
│ └─ ShaderLibrary + .colorEffect / .distortionEffect / .layerEffect
│
└─ Spring physics / timing configuration
└─ → core-animations.md (Spring Configurations section)
API Availability
| API |
Minimum Version |
Reference |
withAnimation |
iOS 13 |
core-animations.md |
.animation(_:value:) |
iOS 13 |
core-animations.md |
.spring(response:dampingFraction:) |
iOS 13 |
core-animations.md |
.matchedGeometryEffect |
iOS 14 |
transitions.md |
.transition(.push(from:)) |
iOS 16 |
transitions.md |
.contentTransition(.numericText()) |
iOS 16 |
transitions.md |
PhaseAnimator |
iOS 17 |
phase-keyframe-animators.md |
KeyframeAnimator |
iOS 17 |
phase-keyframe-animators.md |
.spring(duration:bounce:) |
iOS 17 |
core-animations.md |
Spring presets (.bouncy, .snappy, .smooth) |
iOS 17 |
core-animations.md |
withAnimation(_:completionCriteria:_:completion:) |
iOS 17 |
core-animations.md |
.symbolEffect() |
iOS 17 |
symbol-effects.md |
.transition(.blurReplace) |
iOS 17 |
transitions.md |
.contentTransition(.symbolEffect(.replace)) |
iOS 17 |
transitions.md |
Transition protocol / TransitionPhase |
iOS 17 |
transitions.md |
TransactionKey, scoped .animation / .transaction variants |
iOS 17 |
core-animations.md |
KeyframeTimeline, .mapCameraKeyframeAnimator |
iOS 17 |
phase-keyframe-animators.md |
.scrollTransition, .visualEffect |
iOS 17 |
visual-effects.md |
Shaders: .colorEffect / .distortionEffect / .layerEffect |
iOS 17 |
visual-effects.md |
MeshGradient |
iOS 18 |
visual-effects.md |
TextRenderer / TextAttribute |
iOS 18 |
visual-effects.md |
.matchedTransitionSource |
iOS 18 |
transitions.md |
.navigationTransition(.zoom) — push, sheet, fullScreenCover |
iOS 18 |
transitions.md |
UIViewController.preferredTransition = .zoom |
iOS 18 |
transitions.md |
UIView.animate(_: Animation) / context.animate |
iOS 18 |
transitions.md |
Top 5 Mistakes — Quick Reference
| # |
Mistake |
Fix |
Details |
| 1 |
spring(response:bounce:) — mixing parameter generations |
Use either spring(response:dampingFraction:) (iOS 13) or spring(duration:bounce:) (iOS 17) |
core-animations.md |
| 2 |
.animation(.spring()) without value: parameter |
Always pass value: — the no-value variant is deprecated (iOS 15) |
core-animations.md |
| 3 |
Wrong PhaseAnimator closure signature |
PhaseAnimator(phases) { content, phase in } — not { phase in } |
phase-keyframe-animators.md |
| 4 |
Using matchedGeometryEffect for NavigationStack transitions |
Use matchedTransitionSource + .navigationTransition(.zoom) on iOS 18+ |
transitions.md |
| 5 |
Using withAnimation for SF Symbol effects |
Use .symbolEffect() modifier instead |
symbol-effects.md |
Review Checklist
When reviewing animation code, verify:
Reference Files
| File |
Content |
| core-animations.md |
withAnimation, springs, completions, transactions + TransactionKey, timing curves, Animatable mechanics, fluid-interface principles (momentum projection, rubber-banding, hysteresis) |
| phase-keyframe-animators.md |
PhaseAnimator, KeyframeAnimator, KeyframeTimeline, MapKit camera keyframes, custom animations |
| transitions.md |
View transitions, custom Transition protocol, matched geometry, navigation/zoom transitions (SwiftUI + UIKit), interruptibility, UIKit↔SwiftUI bridging, gesture-driven springs |
| symbol-effects.md |
SF Symbol effects, accessibility |
| visual-effects.md |
scrollTransition, visualEffect, MeshGradient, TextRenderer, shader effects (colorEffect/distortionEffect/layerEffect), effect pipelines, semantic alignment guides |
1---
2name: animation-patterns
3description: SwiftUI animation patterns including springs, transitions, PhaseAnimator, KeyframeAnimator, SF Symbol effects, scroll-driven effects, mesh gradients, text renderers, and shader effects. Use when implementing, reviewing, or fixing animation or visual-effect code on iOS/macOS.
4allowed-tools: [Read, Glob, Grep]
5last_verified: 2026-07-16
6review_by: 2027-06-22
7os_version: iOS 27 / macOS 27
8---
9
10# Animation Patterns
11
12Correct API shapes and patterns for SwiftUI animations. Prevents the most common mistakes: mixed spring parameter generations, wrong PhaseAnimator/KeyframeAnimator closure signatures, and using matchedGeometryEffect where matchedTransitionSource is needed.
13
14## When This Skill Activates
15
16Use this skill when the user:
17- Asks to add, fix, or review **animation** code
18- Mentions **spring**, **bounce**, or **snappy** animations
19- Wants **view transitions** (insertion/removal, hero, zoom)
20- Asks about **PhaseAnimator** or **KeyframeAnimator**
21- Wants **SF Symbol effects** (bounce, pulse, wiggle, breathe)
22- Mentions **matchedGeometryEffect** or **matchedTransitionSource**
23- Asks about **reduce motion** / animation accessibility
24- Wants to sequence or chain animations
25- Mentions **withAnimation**, **animation completions**, or **Transaction**
26- Wants **scroll-driven effects** (parallax, carousels, `scrollTransition`, `visualEffect`)
27- Mentions **MeshGradient**, **TextRenderer**, or **Metal shader effects** (`colorEffect`, `distortionEffect`, `layerEffect`)
28
29## Decision Tree
30
31Choose the right reference file based on what the user needs:
32
33```
34What are you animating?
35│
36├─ A state change (opacity, position, color)
37│ └─ → core-animations.md
38│ ├─ withAnimation { } — explicit animation
39│ ├─ .animation(_:value:) — implicit animation
40│ └─ Spring configuration — .spring, .bouncy, .snappy, .smooth
41│
42├─ A multi-step / sequenced animation
43│ └─ → phase-keyframe-animators.md (PhaseAnimator)
44│ └─ Cycles through discrete phases automatically or on trigger
45│
46├─ A complex multi-property animation (scale + rotation + offset)
47│ └─ → phase-keyframe-animators.md (KeyframeAnimator)
48│ └─ Timeline-based keyframes with per-property tracks
49│
50├─ A view appearing / disappearing
51│ └─ → transitions.md
52│ ├─ .transition() — insertion/removal
53│ ├─ .contentTransition() — text/symbol changes
54│ └─ .asymmetric() — different in/out
55│
56├─ A hero / zoom navigation transition
57│ └─ → transitions.md (matchedTransitionSource section)
58│ ├─ iOS 18+: matchedTransitionSource + .navigationTransition(.zoom)
59│ ├─ UIKit: preferredTransition = .zoom (capture stable IDs, never views)
60│ └─ iOS 14+: matchedGeometryEffect (NOT for NavigationStack)
61│
62├─ A UIKit view driven by SwiftUI state or gestures
63│ └─ → transitions.md (Bridging UIKit and SwiftUI Animations)
64│ └─ UIView.animate(.spring(...)) / context.animate in updateUIView
65│
66├─ An SF Symbol animation
67│ └─ → symbol-effects.md
68│ └─ .symbolEffect(.bounce), .pulse, .wiggle, .breathe, .rotate
69│
70├─ A scroll-driven, geometry-driven, or shader effect
71│ └─ → visual-effects.md
72│ ├─ .scrollTransition — carousel scale/parallax/caption fades
73│ ├─ .visualEffect — position-based effects without GeometryReader
74│ ├─ MeshGradient — animatable multi-point gradients
75│ ├─ TextRenderer — per-line / per-glyph text animation
76│ └─ ShaderLibrary + .colorEffect / .distortionEffect / .layerEffect
77│
78└─ Spring physics / timing configuration
79 └─ → core-animations.md (Spring Configurations section)
80```
81
82## API Availability
83
84| API | Minimum Version | Reference |
85|-----|----------------|-----------|
86| `withAnimation` | iOS 13 | core-animations.md |
87| `.animation(_:value:)` | iOS 13 | core-animations.md |
88| `.spring(response:dampingFraction:)` | iOS 13 | core-animations.md |
89| `.matchedGeometryEffect` | iOS 14 | transitions.md |
90| `.transition(.push(from:))` | iOS 16 | transitions.md |
91| `.contentTransition(.numericText())` | iOS 16 | transitions.md |
92| `PhaseAnimator` | iOS 17 | phase-keyframe-animators.md |
93| `KeyframeAnimator` | iOS 17 | phase-keyframe-animators.md |
94| `.spring(duration:bounce:)` | iOS 17 | core-animations.md |
95| Spring presets (`.bouncy`, `.snappy`, `.smooth`) | iOS 17 | core-animations.md |
96| `withAnimation(_:completionCriteria:_:completion:)` | iOS 17 | core-animations.md |
97| `.symbolEffect()` | iOS 17 | symbol-effects.md |
98| `.transition(.blurReplace)` | iOS 17 | transitions.md |
99| `.contentTransition(.symbolEffect(.replace))` | iOS 17 | transitions.md |
100| `Transition` protocol / `TransitionPhase` | iOS 17 | transitions.md |
101| `TransactionKey`, scoped `.animation` / `.transaction` variants | iOS 17 | core-animations.md |
102| `KeyframeTimeline`, `.mapCameraKeyframeAnimator` | iOS 17 | phase-keyframe-animators.md |
103| `.scrollTransition`, `.visualEffect` | iOS 17 | visual-effects.md |
104| Shaders: `.colorEffect` / `.distortionEffect` / `.layerEffect` | iOS 17 | visual-effects.md |
105| `MeshGradient` | iOS 18 | visual-effects.md |
106| `TextRenderer` / `TextAttribute` | iOS 18 | visual-effects.md |
107| `.matchedTransitionSource` | iOS 18 | transitions.md |
108| `.navigationTransition(.zoom)` — push, sheet, fullScreenCover | iOS 18 | transitions.md |
109| `UIViewController.preferredTransition = .zoom` | iOS 18 | transitions.md |
110| `UIView.animate(_: Animation)` / `context.animate` | iOS 18 | transitions.md |
111
112## Top 5 Mistakes — Quick Reference
113
114| # | Mistake | Fix | Details |
115|---|---------|-----|---------|
116| 1 | `spring(response:bounce:)` — mixing parameter generations | Use either `spring(response:dampingFraction:)` (iOS 13) or `spring(duration:bounce:)` (iOS 17) | core-animations.md |
117| 2 | `.animation(.spring())` without `value:` parameter | Always pass `value:` — the no-value variant is deprecated (iOS 15) | core-animations.md |
118| 3 | Wrong PhaseAnimator closure signature | `PhaseAnimator(phases) { content, phase in }` — not `{ phase in }` | phase-keyframe-animators.md |
119| 4 | Using `matchedGeometryEffect` for NavigationStack transitions | Use `matchedTransitionSource` + `.navigationTransition(.zoom)` on iOS 18+ | transitions.md |
120| 5 | Using `withAnimation` for SF Symbol effects | Use `.symbolEffect()` modifier instead | symbol-effects.md |
121
122## Review Checklist
123
124When reviewing animation code, verify:
125
126- [ ] **Reduce motion** — animations respect `AccessibilityMotionEffect` or `UIAccessibility.isReduceMotionEnabled`; provide non-motion alternatives
127- [ ] **Duration limits** — no animation exceeds ~0.5s for UI feedback; longer only for decorative/ambient effects
128- [ ] **Spring vs linear** — springs for interactive/physical motion; linear/easeInOut only for opacity fades or progress indicators
129- [ ] **No deprecated APIs** — `.animation(.spring())` without `value:` is deprecated; `.animation(nil)` is replaced by `withTransaction`
130- [ ] **Correct spring generation** — parameter names match the same API generation (never mix `response` with `bounce`)
131- [ ] **Completion handlers** — using `withAnimation(_:completionCriteria:_:completion:)` (iOS 17+), not inventing `.onAnimationCompleted`
132- [ ] **Transition scope** — `.transition()` only affects views inside `if`/`switch` controlled by state; not for views that are always present
133- [ ] **Per-frame closures stay cheap** — KeyframeAnimator `content` closures, `visualEffect` closures, `TextRenderer.draw`, and `Animatable` view bodies run every frame; no allocation, formatting, or layout math inside
134
135## Reference Files
136
137| File | Content |
138|------|---------|
139| [core-animations.md](core-animations.md) | withAnimation, springs, completions, transactions + TransactionKey, timing curves, Animatable mechanics, fluid-interface principles (momentum projection, rubber-banding, hysteresis) |
140| [phase-keyframe-animators.md](phase-keyframe-animators.md) | PhaseAnimator, KeyframeAnimator, KeyframeTimeline, MapKit camera keyframes, custom animations |
141| [transitions.md](transitions.md) | View transitions, custom Transition protocol, matched geometry, navigation/zoom transitions (SwiftUI + UIKit), interruptibility, UIKit↔SwiftUI bridging, gesture-driven springs |
142| [symbol-effects.md](symbol-effects.md) | SF Symbol effects, accessibility |
143| [visual-effects.md](visual-effects.md) | scrollTransition, visualEffect, MeshGradient, TextRenderer, shader effects (colorEffect/distortionEffect/layerEffect), effect pipelines, semantic alignment guides |