--- name: animation-patterns description: SwiftUI animation patterns including springs, transitions, PhaseAnimator, KeyframeAnimator, and SF Symbol effects. Use when implementing, reviewing, or fixing animation code on iOS/macOS. allowed-tools: [Read, Glob, Grep] --- # 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** ## 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) │ └─ iOS 14+: matchedGeometryEffect (NOT for NavigationStack) │ ├─ An SF Symbol animation │ └─ → symbol-effects.md │ └─ .symbolEffect(.bounce), .pulse, .wiggle, .breathe, .rotate │ └─ 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 | | `.matchedTransitionSource` | iOS 18 | transitions.md | | `.navigationTransition(.zoom)` | 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: - [ ] **Reduce motion** — animations respect `AccessibilityMotionEffect` or `UIAccessibility.isReduceMotionEnabled`; provide non-motion alternatives - [ ] **Duration limits** — no animation exceeds ~0.5s for UI feedback; longer only for decorative/ambient effects - [ ] **Spring vs linear** — springs for interactive/physical motion; linear/easeInOut only for opacity fades or progress indicators - [ ] **No deprecated APIs** — `.animation(.spring())` without `value:` is deprecated; `.animation(nil)` is replaced by `withTransaction` - [ ] **Correct spring generation** — parameter names match the same API generation (never mix `response` with `bounce`) - [ ] **Completion handlers** — using `withAnimation(_:completionCriteria:_:completion:)` (iOS 17+), not inventing `.onAnimationCompleted` - [ ] **Transition scope** — `.transition()` only affects views inside `if`/`switch` controlled by state; not for views that are always present ## Reference Files | File | Content | |------|---------| | [core-animations.md](core-animations.md) | withAnimation, springs, completions, transactions, timing curves | | [phase-keyframe-animators.md](phase-keyframe-animators.md) | PhaseAnimator, KeyframeAnimator, custom animations | | [transitions.md](transitions.md) | View transitions, matched geometry, navigation transitions | | [symbol-effects.md](symbol-effects.md) | SF Symbol effects, accessibility |