swift-code-reviewer-skill 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +43 -169
  2. package/README.md +43 -2
  3. package/SKILL.md +194 -711
  4. package/bin/install.js +1 -1
  5. package/package.json +2 -1
  6. package/references/agent-loop-feedback.md +148 -0
  7. package/references/companion-skills.md +70 -0
  8. package/references/spec-adherence.md +157 -0
  9. package/skills/README.md +43 -0
  10. package/skills/swift-concurrency/NOTICE.md +18 -0
  11. package/skills/swift-concurrency/SKILL.md +235 -0
  12. package/skills/swift-concurrency/references/actors.md +640 -0
  13. package/skills/swift-concurrency/references/async-await-basics.md +249 -0
  14. package/skills/swift-concurrency/references/async-sequences.md +635 -0
  15. package/skills/swift-concurrency/references/core-data.md +533 -0
  16. package/skills/swift-concurrency/references/glossary.md +96 -0
  17. package/skills/swift-concurrency/references/linting.md +38 -0
  18. package/skills/swift-concurrency/references/memory-management.md +542 -0
  19. package/skills/swift-concurrency/references/migration.md +721 -0
  20. package/skills/swift-concurrency/references/performance.md +574 -0
  21. package/skills/swift-concurrency/references/sendable.md +578 -0
  22. package/skills/swift-concurrency/references/tasks.md +604 -0
  23. package/skills/swift-concurrency/references/testing.md +565 -0
  24. package/skills/swift-concurrency/references/threading.md +452 -0
  25. package/skills/swift-expert/NOTICE.md +18 -0
  26. package/skills/swift-expert/SKILL.md +226 -0
  27. package/skills/swift-expert/references/async-concurrency.md +363 -0
  28. package/skills/swift-expert/references/memory-performance.md +380 -0
  29. package/skills/swift-expert/references/protocol-oriented.md +357 -0
  30. package/skills/swift-expert/references/swiftui-patterns.md +294 -0
  31. package/skills/swift-expert/references/testing-patterns.md +402 -0
  32. package/skills/swift-testing/NOTICE.md +18 -0
  33. package/skills/swift-testing/SKILL.md +295 -0
  34. package/skills/swift-testing/references/async-testing.md +245 -0
  35. package/skills/swift-testing/references/dump-snapshot-testing.md +265 -0
  36. package/skills/swift-testing/references/fixtures.md +193 -0
  37. package/skills/swift-testing/references/integration-testing.md +189 -0
  38. package/skills/swift-testing/references/migration-xctest.md +301 -0
  39. package/skills/swift-testing/references/parameterized-tests.md +171 -0
  40. package/skills/swift-testing/references/snapshot-testing.md +201 -0
  41. package/skills/swift-testing/references/test-doubles.md +243 -0
  42. package/skills/swift-testing/references/test-organization.md +231 -0
  43. package/skills/swiftui-expert-skill/NOTICE.md +18 -0
  44. package/skills/swiftui-expert-skill/SKILL.md +281 -0
  45. package/skills/swiftui-expert-skill/references/accessibility-patterns.md +151 -0
  46. package/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
  47. package/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
  48. package/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
  49. package/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  50. package/skills/swiftui-expert-skill/references/charts.md +602 -0
  51. package/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
  52. package/skills/swiftui-expert-skill/references/latest-apis.md +464 -0
  53. package/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  54. package/skills/swiftui-expert-skill/references/liquid-glass.md +414 -0
  55. package/skills/swiftui-expert-skill/references/list-patterns.md +394 -0
  56. package/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
  57. package/skills/swiftui-expert-skill/references/macos-views.md +357 -0
  58. package/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  59. package/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
  60. package/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  61. package/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  62. package/skills/swiftui-expert-skill/references/state-management.md +417 -0
  63. package/skills/swiftui-expert-skill/references/view-structure.md +389 -0
  64. package/skills/swiftui-ui-patterns/NOTICE.md +18 -0
  65. package/skills/swiftui-ui-patterns/SKILL.md +95 -0
  66. package/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
  67. package/skills/swiftui-ui-patterns/references/async-state.md +96 -0
  68. package/skills/swiftui-ui-patterns/references/components-index.md +50 -0
  69. package/skills/swiftui-ui-patterns/references/controls.md +57 -0
  70. package/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
  71. package/skills/swiftui-ui-patterns/references/focus.md +90 -0
  72. package/skills/swiftui-ui-patterns/references/form.md +97 -0
  73. package/skills/swiftui-ui-patterns/references/grids.md +71 -0
  74. package/skills/swiftui-ui-patterns/references/haptics.md +71 -0
  75. package/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  76. package/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  77. package/skills/swiftui-ui-patterns/references/list.md +86 -0
  78. package/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  79. package/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
  80. package/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  81. package/skills/swiftui-ui-patterns/references/media.md +73 -0
  82. package/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
  83. package/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
  84. package/skills/swiftui-ui-patterns/references/overlay.md +45 -0
  85. package/skills/swiftui-ui-patterns/references/performance.md +62 -0
  86. package/skills/swiftui-ui-patterns/references/previews.md +48 -0
  87. package/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  88. package/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
  89. package/skills/swiftui-ui-patterns/references/searchable.md +71 -0
  90. package/skills/swiftui-ui-patterns/references/sheets.md +155 -0
  91. package/skills/swiftui-ui-patterns/references/split-views.md +72 -0
  92. package/skills/swiftui-ui-patterns/references/tabview.md +114 -0
  93. package/skills/swiftui-ui-patterns/references/theming.md +71 -0
  94. package/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
  95. package/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
@@ -0,0 +1,403 @@
1
+ # SwiftUI Advanced Animations
2
+
3
+ Transactions, phase animations (iOS 17+), keyframe animations (iOS 17+), completion handlers (iOS 17+), and `@Animatable` macro (iOS 26+).
4
+
5
+ ## Table of Contents
6
+ - [Transactions](#transactions)
7
+ - [Phase Animations (iOS 17+)](#phase-animations-ios-17)
8
+ - [Keyframe Animations (iOS 17+)](#keyframe-animations-ios-17)
9
+ - [Animation Completion Handlers (iOS 17+)](#animation-completion-handlers-ios-17)
10
+ - [@Animatable Macro (iOS 26+)](#animatable-macro-ios-26)
11
+
12
+ ---
13
+
14
+ ## Transactions
15
+
16
+ The underlying mechanism for all animations in SwiftUI.
17
+
18
+ ### Basic Usage
19
+
20
+ ```swift
21
+ // withAnimation is shorthand for withTransaction
22
+ withAnimation(.default) { flag.toggle() }
23
+
24
+ // Equivalent explicit transaction
25
+ var transaction = Transaction(animation: .default)
26
+ withTransaction(transaction) { flag.toggle() }
27
+ ```
28
+
29
+ ### The .transaction Modifier
30
+
31
+ ```swift
32
+ Rectangle()
33
+ .frame(width: flag ? 100 : 50, height: 50)
34
+ .transaction { t in
35
+ t.animation = .default
36
+ }
37
+ ```
38
+
39
+ **Note:** This behaves like the deprecated `.animation(_:)` without value parameter - it animates on every state change.
40
+
41
+ ### Animation Precedence
42
+
43
+ **Implicit animations override explicit animations** (later in view tree wins).
44
+
45
+ ```swift
46
+ Button("Tap") {
47
+ withAnimation(.linear) { flag.toggle() }
48
+ }
49
+ .animation(.bouncy, value: flag) // .bouncy wins!
50
+ ```
51
+
52
+ ### Disabling Animations
53
+
54
+ ```swift
55
+ // Prevent implicit animations from overriding
56
+ .transaction { t in
57
+ t.disablesAnimations = true
58
+ }
59
+
60
+ // Remove animation entirely
61
+ .transaction { $0.animation = nil }
62
+ ```
63
+
64
+ ### Custom Transaction Keys (iOS 17+)
65
+
66
+ Pass metadata through transactions.
67
+
68
+ ```swift
69
+ struct ChangeSourceKey: TransactionKey {
70
+ static let defaultValue: String = "unknown"
71
+ }
72
+
73
+ extension Transaction {
74
+ var changeSource: String {
75
+ get { self[ChangeSourceKey.self] }
76
+ set { self[ChangeSourceKey.self] = newValue }
77
+ }
78
+ }
79
+
80
+ // Set source
81
+ var transaction = Transaction(animation: .default)
82
+ transaction.changeSource = "server"
83
+ withTransaction(transaction) { flag.toggle() }
84
+
85
+ // Read in view tree
86
+ .transaction { t in
87
+ if t.changeSource == "server" {
88
+ t.animation = .smooth
89
+ } else {
90
+ t.animation = .bouncy
91
+ }
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Phase Animations (iOS 17+)
98
+
99
+ Cycle through discrete phases automatically. Each phase change is a separate animation.
100
+
101
+ ### Basic Usage
102
+
103
+ ```swift
104
+ // GOOD - triggered phase animation
105
+ Button("Shake") { trigger += 1 }
106
+ .phaseAnimator(
107
+ [0.0, -10.0, 10.0, -5.0, 5.0, 0.0],
108
+ trigger: trigger
109
+ ) { content, offset in
110
+ content.offset(x: offset)
111
+ }
112
+
113
+ // Infinite loop (no trigger)
114
+ Circle()
115
+ .phaseAnimator([1.0, 1.2, 1.0]) { content, scale in
116
+ content.scaleEffect(scale)
117
+ }
118
+ ```
119
+
120
+ ### Enum Phases (Recommended for Clarity)
121
+
122
+ ```swift
123
+ // GOOD - enum phases are self-documenting
124
+ enum BouncePhase: CaseIterable {
125
+ case initial, up, down, settle
126
+
127
+ var scale: CGFloat {
128
+ switch self {
129
+ case .initial: 1.0
130
+ case .up: 1.2
131
+ case .down: 0.9
132
+ case .settle: 1.0
133
+ }
134
+ }
135
+ }
136
+
137
+ Circle()
138
+ .phaseAnimator(BouncePhase.allCases, trigger: trigger) { content, phase in
139
+ content.scaleEffect(phase.scale)
140
+ }
141
+ ```
142
+
143
+ ### Custom Timing Per Phase
144
+
145
+ ```swift
146
+ .phaseAnimator([0, -20, 20], trigger: trigger) { content, offset in
147
+ content.offset(x: offset)
148
+ } animation: { phase in
149
+ switch phase {
150
+ case -20: .bouncy
151
+ case 20: .linear
152
+ default: .smooth
153
+ }
154
+ }
155
+ ```
156
+
157
+ ### Good vs Bad
158
+
159
+ ```swift
160
+ // GOOD - use phaseAnimator for multi-step sequences
161
+ .phaseAnimator([0, -10, 10, 0], trigger: trigger) { content, offset in
162
+ content.offset(x: offset)
163
+ }
164
+
165
+ // BAD - manual DispatchQueue sequencing
166
+ Button("Animate") {
167
+ withAnimation(.easeOut(duration: 0.1)) { offset = -10 }
168
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
169
+ withAnimation { offset = 10 }
170
+ }
171
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
172
+ withAnimation { offset = 0 }
173
+ }
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Keyframe Animations (iOS 17+)
180
+
181
+ Precise timing control with exact values at specific times.
182
+
183
+ ### Basic Usage
184
+
185
+ ```swift
186
+ Button("Bounce") { trigger += 1 }
187
+ .keyframeAnimator(
188
+ initialValue: AnimationValues(),
189
+ trigger: trigger
190
+ ) { content, value in
191
+ content
192
+ .scaleEffect(value.scale)
193
+ .offset(y: value.verticalOffset)
194
+ } keyframes: { _ in
195
+ KeyframeTrack(\.scale) {
196
+ SpringKeyframe(1.2, duration: 0.15)
197
+ SpringKeyframe(0.9, duration: 0.1)
198
+ SpringKeyframe(1.0, duration: 0.15)
199
+ }
200
+ KeyframeTrack(\.verticalOffset) {
201
+ LinearKeyframe(-20, duration: 0.15)
202
+ LinearKeyframe(0, duration: 0.25)
203
+ }
204
+ }
205
+
206
+ struct AnimationValues {
207
+ var scale: CGFloat = 1.0
208
+ var verticalOffset: CGFloat = 0
209
+ }
210
+ ```
211
+
212
+ ### Keyframe Types
213
+
214
+ | Type | Behavior |
215
+ |------|----------|
216
+ | `CubicKeyframe` | Smooth interpolation |
217
+ | `LinearKeyframe` | Straight-line interpolation |
218
+ | `SpringKeyframe` | Spring physics |
219
+ | `MoveKeyframe` | Instant jump (no interpolation) |
220
+
221
+ ### Multiple Synchronized Tracks
222
+
223
+ Tracks run **in parallel**, each animating one property.
224
+
225
+ ```swift
226
+ // GOOD - bell shake with synchronized rotation and scale
227
+ struct BellAnimation {
228
+ var rotation: Double = 0
229
+ var scale: CGFloat = 1.0
230
+ }
231
+
232
+ Image(systemName: "bell.fill")
233
+ .keyframeAnimator(
234
+ initialValue: BellAnimation(),
235
+ trigger: trigger
236
+ ) { content, value in
237
+ content
238
+ .rotationEffect(.degrees(value.rotation))
239
+ .scaleEffect(value.scale)
240
+ } keyframes: { _ in
241
+ KeyframeTrack(\.rotation) {
242
+ CubicKeyframe(15, duration: 0.1)
243
+ CubicKeyframe(-15, duration: 0.1)
244
+ CubicKeyframe(10, duration: 0.1)
245
+ CubicKeyframe(-10, duration: 0.1)
246
+ CubicKeyframe(0, duration: 0.1)
247
+ }
248
+ KeyframeTrack(\.scale) {
249
+ CubicKeyframe(1.1, duration: 0.25)
250
+ CubicKeyframe(1.0, duration: 0.25)
251
+ }
252
+ }
253
+
254
+ // BAD - manual timer-based animation
255
+ Image(systemName: "bell.fill")
256
+ .onTapGesture {
257
+ withAnimation(.easeOut(duration: 0.1)) { rotation = 15 }
258
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
259
+ withAnimation { rotation = -15 }
260
+ }
261
+ // ... more manual timing - error prone
262
+ }
263
+ ```
264
+
265
+ ### KeyframeTimeline (iOS 17+)
266
+
267
+ Query animation values directly for testing or non-SwiftUI use.
268
+
269
+ ```swift
270
+ let timeline = KeyframeTimeline(initialValue: AnimationValues()) {
271
+ KeyframeTrack(\.scale) {
272
+ CubicKeyframe(1.2, duration: 0.25)
273
+ CubicKeyframe(1.0, duration: 0.25)
274
+ }
275
+ }
276
+
277
+ let midpoint = timeline.value(time: 0.25)
278
+ print(midpoint.scale) // Value at 0.25 seconds
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Animation Completion Handlers (iOS 17+)
284
+
285
+ Execute code when animations finish.
286
+
287
+ ### With withAnimation
288
+
289
+ ```swift
290
+ // GOOD - completion with withAnimation
291
+ Button("Animate") {
292
+ withAnimation(.spring) {
293
+ isExpanded.toggle()
294
+ } completion: {
295
+ showNextStep = true
296
+ }
297
+ }
298
+ ```
299
+
300
+ ### With Transaction (For Reexecution)
301
+
302
+ ```swift
303
+ // GOOD - completion fires on every trigger change
304
+ Circle()
305
+ .scaleEffect(bounceCount % 2 == 0 ? 1.0 : 1.2)
306
+ .transaction(value: bounceCount) { transaction in
307
+ transaction.animation = .spring
308
+ transaction.addAnimationCompletion {
309
+ message = "Bounce \(bounceCount) complete"
310
+ }
311
+ }
312
+
313
+ // BAD - completion only fires ONCE (no value parameter)
314
+ Circle()
315
+ .scaleEffect(bounceCount % 2 == 0 ? 1.0 : 1.2)
316
+ .animation(.spring, value: bounceCount)
317
+ .transaction { transaction in // No value!
318
+ transaction.addAnimationCompletion {
319
+ completionCount += 1 // Only fires once, ever
320
+ }
321
+ }
322
+ ```
323
+
324
+ ---
325
+
326
+ ## @Animatable Macro (iOS 26+)
327
+
328
+ The `@Animatable` macro auto-synthesizes `animatableData` from all animatable stored properties, eliminating verbose manual conformance. Use `@AnimatableIgnored` to exclude properties that should not animate.
329
+
330
+ ### Before (Manual)
331
+
332
+ ```swift
333
+ struct Wedge: Shape {
334
+ var startAngle: Angle
335
+ var endAngle: Angle
336
+ var drawClockwise: Bool
337
+
338
+ var animatableData: AnimatablePair<Double, Double> {
339
+ get { AnimatablePair(startAngle.radians, endAngle.radians) }
340
+ set {
341
+ startAngle = .radians(newValue.first)
342
+ endAngle = .radians(newValue.second)
343
+ }
344
+ }
345
+
346
+ func path(in rect: CGRect) -> Path { /* ... */ }
347
+ }
348
+ ```
349
+
350
+ ### After (@Animatable)
351
+
352
+ ```swift
353
+ @Animatable
354
+ struct Wedge: Shape {
355
+ var startAngle: Angle
356
+ var endAngle: Angle
357
+ @AnimatableIgnored var drawClockwise: Bool
358
+
359
+ func path(in rect: CGRect) -> Path { /* ... */ }
360
+ }
361
+ ```
362
+
363
+ ### When to Use
364
+ - **Prefer `@Animatable`** for any custom `Shape`, `AnimatableModifier`, or type conforming to `Animatable` with multiple properties
365
+ - **Use `@AnimatableIgnored`** for properties that control behavior but should not interpolate (e.g., directions, flags, identifiers)
366
+ - The macro works with any type conforming to `Animatable`, not just `Shape`
367
+
368
+ > Source: "What's new in SwiftUI" (WWDC25, session 256)
369
+
370
+ ---
371
+
372
+ ## Quick Reference
373
+
374
+ ### Transactions (All iOS versions)
375
+ - `withTransaction` is the explicit form of `withAnimation`
376
+ - Implicit animations override explicit (later in view tree wins)
377
+ - Use `disablesAnimations` to prevent override
378
+ - Use `.transaction { $0.animation = nil }` to remove animation
379
+
380
+ ### Custom Transaction Keys (iOS 17+)
381
+ - Pass metadata through animation system via `TransactionKey`
382
+
383
+ ### Phase Animations (iOS 17+)
384
+ - Use for multi-step sequences returning to start
385
+ - Prefer enum phases for clarity
386
+ - Each phase change is a separate animation
387
+ - Use `trigger` parameter for one-shot animations
388
+
389
+ ### Keyframe Animations (iOS 17+)
390
+ - Use for precise timing control
391
+ - Tracks run in parallel
392
+ - Use `KeyframeTimeline` for testing/advanced use
393
+ - Prefer over manual DispatchQueue timing
394
+
395
+ ### Completion Handlers (iOS 17+)
396
+ - Use `withAnimation(.animation) { } completion: { }` for one-shot completion handlers
397
+ - Use `.transaction(value:)` for handlers that should refire on every value change
398
+ - Without `value:` parameter, completion only fires once
399
+
400
+ ### @Animatable Macro (iOS 26+)
401
+ - Use `@Animatable` to auto-synthesize `animatableData` from stored properties
402
+ - Use `@AnimatableIgnored` to exclude non-animatable properties
403
+ - Replaces verbose manual `animatableData` getters/setters
@@ -0,0 +1,284 @@
1
+ # SwiftUI Animation Basics
2
+
3
+ Core animation concepts, implicit vs explicit animations, timing curves, and performance patterns.
4
+
5
+ ## Table of Contents
6
+ - [Core Concepts](#core-concepts)
7
+ - [Implicit Animations](#implicit-animations)
8
+ - [Explicit Animations](#explicit-animations)
9
+ - [Animation Placement](#animation-placement)
10
+ - [Selective Animation](#selective-animation)
11
+ - [Timing Curves](#timing-curves)
12
+ - [Animation Performance](#animation-performance)
13
+ - [Disabling Animations](#disabling-animations)
14
+ - [Debugging](#debugging)
15
+
16
+ ---
17
+
18
+ ## Core Concepts
19
+
20
+ State changes trigger view updates. SwiftUI provides mechanisms to animate these changes.
21
+
22
+ **Animation Process:**
23
+ 1. State change triggers view tree re-evaluation
24
+ 2. SwiftUI compares new tree to current render tree
25
+ 3. Animatable properties are identified and interpolated (~60 fps)
26
+
27
+ **Key Characteristics:**
28
+ - Animations are additive and cancelable
29
+ - Always start from current render tree state
30
+ - Blend smoothly when interrupted
31
+
32
+ ---
33
+
34
+ ## Implicit Animations
35
+
36
+ Use `.animation(_:value:)` to animate when a specific value changes.
37
+
38
+ ```swift
39
+ // GOOD - uses value parameter
40
+ Rectangle()
41
+ .frame(width: isExpanded ? 200 : 100, height: 50)
42
+ .animation(.spring, value: isExpanded)
43
+ .onTapGesture { isExpanded.toggle() }
44
+
45
+ // BAD - deprecated, animates all changes unexpectedly
46
+ Rectangle()
47
+ .frame(width: isExpanded ? 200 : 100, height: 50)
48
+ .animation(.spring) // Deprecated!
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Explicit Animations
54
+
55
+ Use `withAnimation` for event-driven state changes.
56
+
57
+ ```swift
58
+ // GOOD - explicit animation
59
+ Button("Toggle") {
60
+ withAnimation(.spring) {
61
+ isExpanded.toggle()
62
+ }
63
+ }
64
+
65
+ // BAD - no animation context
66
+ Button("Toggle") {
67
+ isExpanded.toggle() // Abrupt change
68
+ }
69
+ ```
70
+
71
+ **When to use which:**
72
+ - **Implicit**: Animations tied to specific value changes, precise view tree scope
73
+ - **Explicit**: Event-driven animations (button taps, gestures)
74
+
75
+ ---
76
+
77
+ ## Animation Placement
78
+
79
+ Place animation modifiers after the properties they should animate.
80
+
81
+ ```swift
82
+ // GOOD - animation after properties
83
+ Rectangle()
84
+ .frame(width: isExpanded ? 200 : 100, height: 50)
85
+ .foregroundStyle(isExpanded ? .blue : .red)
86
+ .animation(.default, value: isExpanded) // Animates both
87
+
88
+ // BAD - animation before properties
89
+ Rectangle()
90
+ .animation(.default, value: isExpanded) // Too early!
91
+ .frame(width: isExpanded ? 200 : 100, height: 50)
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Selective Animation
97
+
98
+ Animate only specific properties using multiple animation modifiers or scoped animations.
99
+
100
+ ```swift
101
+ // GOOD - selective animation
102
+ Rectangle()
103
+ .frame(width: isExpanded ? 200 : 100, height: 50)
104
+ .animation(.spring, value: isExpanded) // Animate size
105
+ .foregroundStyle(isExpanded ? .blue : .red)
106
+ .animation(nil, value: isExpanded) // Don't animate color
107
+
108
+ // iOS 17+ scoped animation
109
+ Rectangle()
110
+ .foregroundStyle(isExpanded ? .blue : .red) // Not animated
111
+ .animation(.spring) {
112
+ $0.frame(width: isExpanded ? 200 : 100, height: 50) // Animated
113
+ }
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Timing Curves
119
+
120
+ ### Built-in Curves
121
+
122
+ | Curve | Use Case |
123
+ |-------|----------|
124
+ | `.spring` | Interactive elements, most UI |
125
+ | `.easeInOut` | Appearance changes |
126
+ | `.bouncy` | Playful feedback (iOS 17+) |
127
+ | `.linear` | Progress indicators only |
128
+
129
+ ### Modifiers
130
+
131
+ ```swift
132
+ .animation(.default.speed(2.0), value: flag) // 2x faster
133
+ .animation(.default.delay(0.5), value: flag) // Delayed start
134
+ .animation(.default.repeatCount(3, autoreverses: true), value: flag)
135
+ ```
136
+
137
+ ### Good vs Bad Timing
138
+
139
+ ```swift
140
+ // GOOD - appropriate timing for interaction type
141
+ Button("Tap") {
142
+ withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
143
+ isActive.toggle()
144
+ }
145
+ }
146
+ .scaleEffect(isActive ? 0.95 : 1.0)
147
+
148
+ // BAD - too slow for button feedback
149
+ Button("Tap") {
150
+ withAnimation(.easeInOut(duration: 1.0)) { // Way too slow!
151
+ isActive.toggle()
152
+ }
153
+ }
154
+
155
+ // BAD - linear feels robotic
156
+ Rectangle()
157
+ .animation(.linear(duration: 0.5), value: isActive) // Mechanical
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Animation Performance
163
+
164
+ ### Prefer Transforms Over Layout
165
+
166
+ ```swift
167
+ // GOOD - GPU accelerated transforms
168
+ Rectangle()
169
+ .frame(width: 100, height: 100)
170
+ .scaleEffect(isActive ? 1.5 : 1.0) // Fast
171
+ .offset(x: isActive ? 50 : 0) // Fast
172
+ .rotationEffect(.degrees(isActive ? 45 : 0)) // Fast
173
+ .animation(.spring, value: isActive)
174
+
175
+ // BAD - layout changes are expensive
176
+ Rectangle()
177
+ .frame(width: isActive ? 150 : 100, height: isActive ? 150 : 100) // Expensive
178
+ .padding(isActive ? 50 : 0) // Expensive
179
+ ```
180
+
181
+ ### Narrow Animation Scope
182
+
183
+ ```swift
184
+ // GOOD - animation scoped to specific subview
185
+ VStack {
186
+ HeaderView() // Not affected
187
+ ExpandableContent(isExpanded: isExpanded)
188
+ .animation(.spring, value: isExpanded) // Only this
189
+ FooterView() // Not affected
190
+ }
191
+
192
+ // BAD - animation at root
193
+ VStack {
194
+ HeaderView()
195
+ ExpandableContent(isExpanded: isExpanded)
196
+ FooterView()
197
+ }
198
+ .animation(.spring, value: isExpanded) // Animates everything
199
+ ```
200
+
201
+ ### Avoid Animation in Hot Paths
202
+
203
+ ```swift
204
+ // GOOD - gate by threshold
205
+ .onPreferenceChange(ScrollOffsetKey.self) { offset in
206
+ let shouldShow = offset.y < -50
207
+ if shouldShow != showTitle { // Only when crossing threshold
208
+ withAnimation(.easeOut(duration: 0.2)) {
209
+ showTitle = shouldShow
210
+ }
211
+ }
212
+ }
213
+
214
+ // BAD - animating every scroll change
215
+ .onPreferenceChange(ScrollOffsetKey.self) { offset in
216
+ withAnimation { // Fires constantly!
217
+ self.offset = offset.y
218
+ }
219
+ }
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Disabling Animations
225
+
226
+ ```swift
227
+ // GOOD - disable with transaction
228
+ Text("Count: \(count)")
229
+ .transaction { $0.animation = nil }
230
+
231
+ // GOOD - disable from parent context
232
+ DataView()
233
+ .transaction { $0.disablesAnimations = true }
234
+
235
+ // BAD - hacky zero duration
236
+ Text("Count: \(count)")
237
+ .animation(.linear(duration: 0), value: count) // Hacky
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Debugging
243
+
244
+ ```swift
245
+ // Slow down for inspection
246
+ #if DEBUG
247
+ .animation(.linear(duration: 3.0).speed(0.2), value: isExpanded)
248
+ #else
249
+ .animation(.spring, value: isExpanded)
250
+ #endif
251
+
252
+ // Debug modifier to log values
253
+ struct AnimationDebugModifier: ViewModifier, Animatable {
254
+ var value: Double
255
+ var animatableData: Double {
256
+ get { value }
257
+ set {
258
+ value = newValue
259
+ print("Animation: \(newValue)")
260
+ }
261
+ }
262
+ func body(content: Content) -> some View {
263
+ content.opacity(value)
264
+ }
265
+ }
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Quick Reference
271
+
272
+ ### Do
273
+ - Use `.animation(_:value:)` with value parameter
274
+ - Use `withAnimation` for event-driven animations
275
+ - Prefer transforms over layout changes
276
+ - Scope animations narrowly
277
+ - Choose appropriate timing curves
278
+
279
+ ### Don't
280
+ - Use deprecated `.animation(_:)` without value
281
+ - Animate layout properties in hot paths
282
+ - Apply broad animations at root level
283
+ - Use linear timing for UI (feels robotic)
284
+ - Animate on every frame in scroll handlers