|
| 1 | +--- |
| 2 | +name: animation-patterns |
| 3 | +description: SwiftUI animation patterns including springs, transitions, PhaseAnimator, KeyframeAnimator, and SF Symbol effects. Use when implementing, reviewing, or fixing animation code on iOS/macOS. |
| 4 | +allowed-tools: [Read, Glob, Grep] |
| 5 | +--- |
| 6 | + |
| 7 | +# Animation Patterns |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +## When This Skill Activates |
| 12 | + |
| 13 | +Use this skill when the user: |
| 14 | +- Asks to add, fix, or review **animation** code |
| 15 | +- Mentions **spring**, **bounce**, or **snappy** animations |
| 16 | +- Wants **view transitions** (insertion/removal, hero, zoom) |
| 17 | +- Asks about **PhaseAnimator** or **KeyframeAnimator** |
| 18 | +- Wants **SF Symbol effects** (bounce, pulse, wiggle, breathe) |
| 19 | +- Mentions **matchedGeometryEffect** or **matchedTransitionSource** |
| 20 | +- Asks about **reduce motion** / animation accessibility |
| 21 | +- Wants to sequence or chain animations |
| 22 | +- Mentions **withAnimation**, **animation completions**, or **Transaction** |
| 23 | + |
| 24 | +## Decision Tree |
| 25 | + |
| 26 | +Choose the right reference file based on what the user needs: |
| 27 | + |
| 28 | +``` |
| 29 | +What are you animating? |
| 30 | +│ |
| 31 | +├─ A state change (opacity, position, color) |
| 32 | +│ └─ → core-animations.md |
| 33 | +│ ├─ withAnimation { } — explicit animation |
| 34 | +│ ├─ .animation(_:value:) — implicit animation |
| 35 | +│ └─ Spring configuration — .spring, .bouncy, .snappy, .smooth |
| 36 | +│ |
| 37 | +├─ A multi-step / sequenced animation |
| 38 | +│ └─ → phase-keyframe-animators.md (PhaseAnimator) |
| 39 | +│ └─ Cycles through discrete phases automatically or on trigger |
| 40 | +│ |
| 41 | +├─ A complex multi-property animation (scale + rotation + offset) |
| 42 | +│ └─ → phase-keyframe-animators.md (KeyframeAnimator) |
| 43 | +│ └─ Timeline-based keyframes with per-property tracks |
| 44 | +│ |
| 45 | +├─ A view appearing / disappearing |
| 46 | +│ └─ → transitions.md |
| 47 | +│ ├─ .transition() — insertion/removal |
| 48 | +│ ├─ .contentTransition() — text/symbol changes |
| 49 | +│ └─ .asymmetric() — different in/out |
| 50 | +│ |
| 51 | +├─ A hero / zoom navigation transition |
| 52 | +│ └─ → transitions.md (matchedTransitionSource section) |
| 53 | +│ ├─ iOS 18+: matchedTransitionSource + .navigationTransition(.zoom) |
| 54 | +│ └─ iOS 14+: matchedGeometryEffect (NOT for NavigationStack) |
| 55 | +│ |
| 56 | +├─ An SF Symbol animation |
| 57 | +│ └─ → symbol-effects.md |
| 58 | +│ └─ .symbolEffect(.bounce), .pulse, .wiggle, .breathe, .rotate |
| 59 | +│ |
| 60 | +└─ Spring physics / timing configuration |
| 61 | + └─ → core-animations.md (Spring Configurations section) |
| 62 | +``` |
| 63 | + |
| 64 | +## API Availability |
| 65 | + |
| 66 | +| API | Minimum Version | Reference | |
| 67 | +|-----|----------------|-----------| |
| 68 | +| `withAnimation` | iOS 13 | core-animations.md | |
| 69 | +| `.animation(_:value:)` | iOS 13 | core-animations.md | |
| 70 | +| `.spring(response:dampingFraction:)` | iOS 13 | core-animations.md | |
| 71 | +| `.matchedGeometryEffect` | iOS 14 | transitions.md | |
| 72 | +| `.transition(.push(from:))` | iOS 16 | transitions.md | |
| 73 | +| `.contentTransition(.numericText())` | iOS 16 | transitions.md | |
| 74 | +| `PhaseAnimator` | iOS 17 | phase-keyframe-animators.md | |
| 75 | +| `KeyframeAnimator` | iOS 17 | phase-keyframe-animators.md | |
| 76 | +| `.spring(duration:bounce:)` | iOS 17 | core-animations.md | |
| 77 | +| Spring presets (`.bouncy`, `.snappy`, `.smooth`) | iOS 17 | core-animations.md | |
| 78 | +| `withAnimation(_:completionCriteria:_:completion:)` | iOS 17 | core-animations.md | |
| 79 | +| `.symbolEffect()` | iOS 17 | symbol-effects.md | |
| 80 | +| `.transition(.blurReplace)` | iOS 17 | transitions.md | |
| 81 | +| `.contentTransition(.symbolEffect(.replace))` | iOS 17 | transitions.md | |
| 82 | +| `.matchedTransitionSource` | iOS 18 | transitions.md | |
| 83 | +| `.navigationTransition(.zoom)` | iOS 18 | transitions.md | |
| 84 | + |
| 85 | +## Top 5 Mistakes — Quick Reference |
| 86 | + |
| 87 | +| # | Mistake | Fix | Details | |
| 88 | +|---|---------|-----|---------| |
| 89 | +| 1 | `spring(response:bounce:)` — mixing parameter generations | Use either `spring(response:dampingFraction:)` (iOS 13) or `spring(duration:bounce:)` (iOS 17) | core-animations.md | |
| 90 | +| 2 | `.animation(.spring())` without `value:` parameter | Always pass `value:` — the no-value variant is deprecated (iOS 15) | core-animations.md | |
| 91 | +| 3 | Wrong PhaseAnimator closure signature | `PhaseAnimator(phases) { content, phase in }` — not `{ phase in }` | phase-keyframe-animators.md | |
| 92 | +| 4 | Using `matchedGeometryEffect` for NavigationStack transitions | Use `matchedTransitionSource` + `.navigationTransition(.zoom)` on iOS 18+ | transitions.md | |
| 93 | +| 5 | Using `withAnimation` for SF Symbol effects | Use `.symbolEffect()` modifier instead | symbol-effects.md | |
| 94 | + |
| 95 | +## Review Checklist |
| 96 | + |
| 97 | +When reviewing animation code, verify: |
| 98 | + |
| 99 | +- [ ] **Reduce motion** — animations respect `AccessibilityMotionEffect` or `UIAccessibility.isReduceMotionEnabled`; provide non-motion alternatives |
| 100 | +- [ ] **Duration limits** — no animation exceeds ~0.5s for UI feedback; longer only for decorative/ambient effects |
| 101 | +- [ ] **Spring vs linear** — springs for interactive/physical motion; linear/easeInOut only for opacity fades or progress indicators |
| 102 | +- [ ] **No deprecated APIs** — `.animation(.spring())` without `value:` is deprecated; `.animation(nil)` is replaced by `withTransaction` |
| 103 | +- [ ] **Correct spring generation** — parameter names match the same API generation (never mix `response` with `bounce`) |
| 104 | +- [ ] **Completion handlers** — using `withAnimation(_:completionCriteria:_:completion:)` (iOS 17+), not inventing `.onAnimationCompleted` |
| 105 | +- [ ] **Transition scope** — `.transition()` only affects views inside `if`/`switch` controlled by state; not for views that are always present |
| 106 | + |
| 107 | +## Reference Files |
| 108 | + |
| 109 | +| File | Content | |
| 110 | +|------|---------| |
| 111 | +| [core-animations.md](core-animations.md) | withAnimation, springs, completions, transactions, timing curves | |
| 112 | +| [phase-keyframe-animators.md](phase-keyframe-animators.md) | PhaseAnimator, KeyframeAnimator, custom animations | |
| 113 | +| [transitions.md](transitions.md) | View transitions, matched geometry, navigation transitions | |
| 114 | +| [symbol-effects.md](symbol-effects.md) | SF Symbol effects, accessibility | |
0 commit comments