@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1

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 (284) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +285 -285
  5. package/package.json +3 -3
  6. package/pipeline/lib/redact.mjs +3 -2
  7. package/pipeline/scripts/_notices.mjs +1 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +13 -1
  9. package/pipeline/scripts/pre-commit-check.sh +4 -0
  10. package/pipeline/skills/.skill-manifest.json +69 -69
  11. package/pipeline/skills/shared/README.md +70 -70
  12. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  13. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  14. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  15. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  16. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  17. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  18. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  19. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  20. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  21. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  22. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  23. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +345 -277
  24. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  25. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +107 -121
  26. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
  27. package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -326
  28. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  29. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  30. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  31. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +335 -360
  32. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  34. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  35. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  37. package/pipeline/skills/shared/external/authentication/SKILL.md +277 -381
  38. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  39. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -178
  40. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  41. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  42. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  43. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  44. package/pipeline/skills/shared/external/background-processing/SKILL.md +274 -384
  45. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  46. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
  47. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  48. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  49. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  50. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  51. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  52. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  53. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  54. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  55. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  56. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +228 -376
  57. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  58. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  59. package/pipeline/skills/shared/external/core-data/SKILL.md +302 -368
  60. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  61. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  62. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  63. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  64. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  65. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  66. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  67. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  68. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  69. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  70. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  71. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  72. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  73. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  74. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  75. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  76. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  77. package/pipeline/skills/shared/external/device-integrity/SKILL.md +236 -353
  78. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  79. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  80. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  81. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  82. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  83. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  84. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  85. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  86. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  87. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  88. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  89. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  90. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  91. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  92. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  93. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  94. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  95. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  96. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  97. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  98. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  99. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  100. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  101. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  102. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  103. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  104. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  105. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  106. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  107. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  108. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  109. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  111. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  112. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  113. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  114. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
  115. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
  116. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
  117. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
  118. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
  119. package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
  120. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
  121. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
  122. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
  123. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
  124. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
  125. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
  126. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
  127. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
  128. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
  129. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
  130. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -267
  131. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  132. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  133. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  134. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  135. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  136. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  137. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  138. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  139. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  140. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  141. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  142. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  143. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  144. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  145. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  146. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  147. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  148. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  149. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  150. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  151. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  152. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  153. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  154. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  155. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  156. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  157. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  158. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  159. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  160. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  161. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  162. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  163. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  164. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  165. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  166. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  167. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  168. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  169. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  170. package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
  171. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  172. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  173. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  174. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  175. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  176. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  177. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +302 -241
  178. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  179. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  180. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  181. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  182. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  183. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  184. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  185. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  186. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  187. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  188. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  189. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  191. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +304 -351
  193. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  194. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  195. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  196. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  197. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  198. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  199. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  200. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  201. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  202. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  203. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  204. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  205. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  206. package/pipeline/skills/shared/external/swift-security/SKILL.md +183 -162
  207. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  208. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  209. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +411 -476
  210. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  211. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  212. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  213. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  214. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  215. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  216. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +375 -491
  217. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  218. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  219. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  220. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +397 -457
  221. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  222. package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
  223. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  224. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
  225. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  226. package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -256
  227. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  228. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  229. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  230. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  231. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  232. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  233. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  234. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  235. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  236. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  237. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  238. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  239. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  240. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  241. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  242. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  243. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  244. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  245. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  246. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  247. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  248. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  249. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  250. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  251. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +201 -168
  252. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  253. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
  254. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  255. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
  256. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  257. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  258. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  259. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  260. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  261. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  262. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  263. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  264. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  265. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  266. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  267. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  268. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  269. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  270. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  271. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  272. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  273. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  274. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  275. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  276. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  277. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  278. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  279. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  280. package/pipeline/skills/shared/external/weatherkit/SKILL.md +160 -315
  281. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  282. package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
  283. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
  284. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,825 +1,613 @@
1
- # SwiftUI Animation Advanced Reference
1
+ # Advanced SwiftUI Animation
2
2
 
3
- Detailed API reference for SwiftUI animation types, protocols, and patterns.
4
- Covers material beyond the SKILL.md summary.
3
+ Depth behind the main skill: custom curves, the full `Spring` API, unit
4
+ curves, richer phase and keyframe patterns, transactions, every transition and
5
+ symbol effect, Reduce Motion handling and performance.
5
6
 
6
7
  ## Contents
7
8
 
8
- - [CustomAnimation Protocol (iOS 17+)](#customanimation-protocol-ios-17)
9
- - [Spring Type -- All Initializer Variants](#spring-type-all-initializer-variants)
10
- - [UnitCurve Types (iOS 17+)](#unitcurve-types-ios-17)
11
- - [PhaseAnimator Deep Patterns](#phaseanimator-deep-patterns)
12
- - [KeyframeAnimator Multi-Track Examples](#keyframeanimator-multi-track-examples)
9
+ - [CustomAnimation (iOS 17+)](#customanimation-ios-17)
10
+ - [Spring, every initializer](#spring-every-initializer)
11
+ - [UnitCurve (iOS 17+)](#unitcurve-ios-17)
12
+ - [PhaseAnimator patterns](#phaseanimator-patterns)
13
+ - [KeyframeAnimator patterns](#keyframeanimator-patterns)
13
14
  - [Transaction and TransactionKey](#transaction-and-transactionkey)
14
- - [Scoped Implicit Animation](#scoped-implicit-animation)
15
- - [All Transition Types (iOS 17+)](#all-transition-types-ios-17)
16
- - [All Symbol Effect Types](#all-symbol-effect-types)
17
- - [Reduce Motion Implementation Patterns](#reduce-motion-implementation-patterns)
18
- - [Animation Performance Tips](#animation-performance-tips)
15
+ - [Choosing an implicit scope](#choosing-an-implicit-scope)
16
+ - [Every transition (iOS 17+)](#every-transition-ios-17)
17
+ - [Every symbol effect](#every-symbol-effect)
18
+ - [Reduce Motion](#reduce-motion)
19
+ - [Performance](#performance)
19
20
 
20
- ## CustomAnimation Protocol (iOS 17+)
21
-
22
- Create entirely custom animation curves by conforming to `CustomAnimation`.
21
+ ## CustomAnimation (iOS 17+)
23
22
 
24
23
  ```swift
25
24
  @preconcurrency protocol CustomAnimation: Hashable, Sendable
26
25
  ```
27
26
 
28
- ### Required Method
27
+ One requirement and two optional hooks:
29
28
 
30
29
  ```swift
30
+ // Required. Return the value at `time`, or nil once the animation is finished.
31
31
  func animate<V: VectorArithmetic>(
32
- value: V,
33
- time: TimeInterval,
34
- context: inout AnimationContext<V>
32
+ value: V, time: TimeInterval, context: inout AnimationContext<V>
35
33
  ) -> V?
36
- ```
37
-
38
- Return the interpolated value at the given time. Return `nil` when the
39
- animation is complete.
40
34
 
41
- ### Optional Methods
42
-
43
- ```swift
35
+ // Optional. Current velocity, used when another animation takes over.
44
36
  func velocity<V: VectorArithmetic>(
45
- value: V,
46
- time: TimeInterval,
47
- context: AnimationContext<V>
37
+ value: V, time: TimeInterval, context: AnimationContext<V>
48
38
  ) -> V?
49
39
 
40
+ // Optional. Return true to absorb a new animation instead of being replaced.
50
41
  func shouldMerge<V: VectorArithmetic>(
51
- previous: Animation,
52
- value: V,
53
- time: TimeInterval,
54
- context: inout AnimationContext<V>
42
+ previous: Animation, value: V, time: TimeInterval, context: inout AnimationContext<V>
55
43
  ) -> Bool
56
44
  ```
57
45
 
58
- ### Full Example: Elastic Ease-In-Out
46
+ `value` is the full distance to travel; the return value is how much of it has
47
+ been covered at `time`.
48
+
49
+ A back-out curve that overshoots slightly and then settles:
59
50
 
60
51
  ```swift
61
- struct ElasticAnimation: CustomAnimation {
62
- let duration: TimeInterval
52
+ struct OvershootAnimation: CustomAnimation {
53
+ var duration: TimeInterval
54
+ var tension: Double = 1.7
63
55
 
64
56
  func animate<V: VectorArithmetic>(
65
- value: V,
66
- time: TimeInterval,
67
- context: inout AnimationContext<V>
57
+ value: V, time: TimeInterval, context: inout AnimationContext<V>
68
58
  ) -> V? {
69
- guard time <= duration else { return nil }
70
- let p = time / duration
71
- let s = sin((20 * p - 11.125) * ((2 * .pi) / 4.5))
72
- let progress: Double
73
- if p < 0.5 {
74
- progress = -(pow(2, 20 * p - 10) * s) / 2
75
- } else {
76
- progress = (pow(2, -20 * p + 10) * s) / 2 + 1
77
- }
59
+ if time > duration { return nil }
60
+ let t = time / duration - 1
61
+ let progress = t * t * ((tension + 1) * t + tension) + 1
78
62
  return value.scaled(by: progress)
79
63
  }
80
64
  }
81
- ```
82
-
83
- ### Ergonomic Extension Pattern
84
65
 
85
- Expose custom animations as static members on `Animation`.
86
-
87
- ```swift
88
66
  extension Animation {
89
- static var elastic: Animation {
90
- elastic(duration: 0.35)
91
- }
67
+ static var overshoot: Animation { overshoot(duration: 0.4) }
92
68
 
93
- static func elastic(duration: TimeInterval) -> Animation {
94
- Animation(ElasticAnimation(duration: duration))
69
+ static func overshoot(duration: TimeInterval) -> Animation {
70
+ Animation(OvershootAnimation(duration: duration))
95
71
  }
96
72
  }
97
73
 
98
- // Usage
99
- withAnimation(.elastic(duration: 0.5)) { isActive.toggle() }
74
+ // withAnimation(.overshoot(duration: 0.5)) { panelOffset = 0 }
100
75
  ```
101
76
 
102
- ### Supporting Types
103
-
104
- | Type | Role |
105
- |---|---|
106
- | `AnimationContext<V>` | Carries environment and per-animation state |
107
- | `AnimationState` | Key-value storage for persisted state |
108
- | `AnimationStateKey` | Protocol for defining custom state keys |
109
-
110
- ## Spring Type -- All Initializer Variants
111
-
112
- ### Perceptual (Preferred)
77
+ Supporting types:
113
78
 
114
- ```swift
115
- Spring(duration: 0.5, bounce: 0.0)
116
- ```
117
-
118
- - `duration` -- Perceptual duration controlling pace. Default `0.5`.
119
- - `bounce` -- Bounciness. `0.0` = no bounce, `1.0` = undamped. Negative values
120
- produce overdamped springs. Default `0.0`.
79
+ - `AnimationContext<V>`: the environment plus state private to one running
80
+ animation.
81
+ - `AnimationState`: a key-value store that persists across `animate` calls.
82
+ - `AnimationStateKey`: the protocol your own state keys conform to.
121
83
 
122
- ### Physical Parameters
84
+ ## Spring, every initializer
123
85
 
124
- ```swift
125
- Spring(mass: 1.0, stiffness: 100.0, damping: 10.0, allowOverDamping: false)
126
- ```
86
+ ### `Spring(duration:bounce:)`
127
87
 
128
- - `mass` -- Mass at end of spring. Default `1.0`.
129
- - `stiffness` -- Spring stiffness coefficient.
130
- - `damping` -- Friction-like drag force.
131
- - `allowOverDamping` -- Permit damping ratio > 1. Default `false`.
88
+ - `duration`: perceived pace. Default `0.5`.
89
+ - `bounce`: `0.0` none, `1.0` undamped, below zero overdamped. Default `0.0`.
132
90
 
133
- ### Response-Based
91
+ ### `Spring(mass:stiffness:damping:allowOverDamping:)`
134
92
 
135
- ```swift
136
- Spring(response: 0.5, dampingRatio: 0.7)
137
- ```
93
+ - `mass`: default `1.0`.
94
+ - `stiffness`: the spring constant.
95
+ - `damping`: the drag that slows it.
96
+ - `allowOverDamping`: permits a damping ratio above 1. Default `false`.
138
97
 
139
- - `response` -- Stiffness expressed as approximate duration in seconds.
140
- - `dampingRatio` -- Fraction of critical damping. `1.0` = critically damped.
98
+ ### `Spring(response:dampingRatio:)`
141
99
 
142
- ### Settling-Based
100
+ - `response`: stiffness expressed as a rough duration in seconds.
101
+ - `dampingRatio`: how much of critical damping is applied, where `1.0` means
102
+ exactly critical.
143
103
 
144
- ```swift
145
- Spring(settlingDuration: 1.0, dampingRatio: 0.8, epsilon: 0.001)
146
- ```
104
+ ### `Spring(settlingDuration:dampingRatio:epsilon:)`
147
105
 
148
- - `settlingDuration` -- Estimated time to come to rest.
149
- - `dampingRatio` -- Fraction of critical damping.
150
- - `epsilon` -- Threshold for considering the spring at rest. Default `0.001`.
106
+ - `settlingDuration`: estimated time until it comes to rest.
107
+ - `epsilon`: how close to rest counts as at rest. Default `0.001`.
151
108
 
152
109
  ### Presets
153
110
 
154
111
  ```swift
155
- Spring.smooth // no bounce
156
- Spring.smooth(duration: 0.5, extraBounce: 0.0)
157
- Spring.snappy // small bounce
158
- Spring.snappy(duration: 0.4, extraBounce: 0.1)
159
- Spring.bouncy // visible bounce
160
- Spring.bouncy(duration: 0.5, extraBounce: 0.2)
112
+ Spring.smooth
113
+ Spring.smooth(duration: 0.6)
114
+ Spring.snappy
115
+ Spring.snappy(duration: 0.35, extraBounce: 0.05)
116
+ Spring.bouncy
117
+ Spring.bouncy(duration: 0.55, extraBounce: 0.15)
161
118
  ```
162
119
 
163
- ### Querying State
120
+ ### Asking a spring questions
164
121
 
165
122
  ```swift
166
- let spring = Spring(duration: 0.5, bounce: 0.3)
167
- let v = spring.value(target: 1.0, initialVelocity: 0.0, time: 0.25)
168
- let vel = spring.velocity(target: 1.0, initialVelocity: 0.0, time: 0.25)
169
- let settle = spring.settlingDuration(target: 1.0, initialVelocity: 0.0, epsilon: 0.001)
123
+ let pad = Spring(duration: 0.45, bounce: 0.2)
124
+ let position = pad.value(target: 100.0, initialVelocity: 0, time: 0.1)
125
+ let speed = pad.velocity(target: 100.0, initialVelocity: 0, time: 0.1)
126
+ let restAt = pad.settlingDuration(target: 100.0, initialVelocity: 0, epsilon: 0.01)
170
127
  ```
171
128
 
172
- ### Parameter Conversion
129
+ Any spring can also be read in the other parameter systems through `mass`,
130
+ `stiffness`, `damping`, `response`, `dampingRatio` and `settlingDuration`.
131
+ For example, a spring with a 0.5 second duration and 0.3 bounce converts to
132
+ mass 1.0, stiffness about 157.9 and damping about 17.6.
173
133
 
174
- ```swift
175
- let spring = Spring(duration: 0.5, bounce: 0.3)
176
- // Access physical equivalents:
177
- spring.mass // 1.0
178
- spring.stiffness // 157.9
179
- spring.damping // 17.6
180
- spring.response
181
- spring.dampingRatio
182
- spring.settlingDuration
183
- ```
134
+ ## UnitCurve (iOS 17+)
184
135
 
185
- ## UnitCurve Types (iOS 17+)
186
-
187
- Map input progress [0,1] to output progress [0,1]. Used with
136
+ A `UnitCurve` maps progress in [0, 1] to eased progress in [0, 1]. Use it with
188
137
  `.timingCurve(_:duration:)`.
189
138
 
190
- ### Built-in Curves
139
+ Built in: `.linear`, `.easeIn`, `.easeOut`, `.easeInOut`, `.circularEaseIn`,
140
+ `.circularEaseOut`, `.circularEaseInOut`.
191
141
 
192
- ```swift
193
- UnitCurve.linear
194
- UnitCurve.easeIn
195
- UnitCurve.easeOut
196
- UnitCurve.easeInOut
197
- UnitCurve.circularEaseIn
198
- UnitCurve.circularEaseOut
199
- UnitCurve.circularEaseInOut
200
- ```
201
-
202
- ### Custom Bezier Curve
142
+ Custom:
203
143
 
204
144
  ```swift
205
- UnitCurve.bezier(
206
- startControlPoint: UnitPoint(x: 0.42, y: 0.0),
207
- endControlPoint: UnitPoint(x: 0.58, y: 1.0)
145
+ let gentle = UnitCurve.bezier(
146
+ startControlPoint: UnitPoint(x: 0.25, y: 0.1),
147
+ endControlPoint: UnitPoint(x: 0.25, y: 1.0)
208
148
  )
149
+ let halfway = gentle.value(at: 0.5)
150
+ let slope = gentle.velocity(at: 0.5)
151
+ let reversed = gentle.inverse // x and y swapped
209
152
  ```
210
153
 
211
- ### Instance Members
154
+ In use:
212
155
 
213
156
  ```swift
214
- let curve = UnitCurve.easeInOut
215
- curve.value(at: 0.5) // output progress at midpoint
216
- curve.velocity(at: 0.5) // rate of change at midpoint
217
- curve.inverse // swaps x and y components
157
+ .animation(.timingCurve(UnitCurve.circularEaseOut, duration: 0.35), value: isOpen)
158
+ .animation(.timingCurve(0.34, 1.56, 0.64, 1, duration: 0.45), value: isOpen)
218
159
  ```
219
160
 
220
- ### Usage with Animation
161
+ ## PhaseAnimator patterns
221
162
 
222
- ```swift
223
- .animation(.timingCurve(UnitCurve.circularEaseIn, duration: 0.4), value: x)
224
-
225
- // Cubic bezier control points
226
- .animation(.timingCurve(0.68, -0.55, 0.27, 1.55, duration: 0.5), value: x)
227
- ```
228
-
229
- ## PhaseAnimator Deep Patterns
230
-
231
- ### Multi-Phase with Complex State
163
+ ### Phases that carry their own values
232
164
 
233
165
  ```swift
234
- enum LoadPhase: CaseIterable {
235
- case ready, loading, spinning, complete
236
-
237
- var scale: Double {
238
- switch self {
239
- case .ready: 1.0
240
- case .loading: 0.9
241
- case .spinning: 1.0
242
- case .complete: 1.1
243
- }
244
- }
166
+ enum UploadPhase: CaseIterable {
167
+ case idle, preparing, sending, done
245
168
 
246
- var rotation: Angle {
247
- switch self {
248
- case .spinning: .degrees(360)
249
- default: .zero
250
- }
251
- }
252
-
253
- var opacity: Double {
254
- self == .loading ? 0.7 : 1.0
255
- }
169
+ var scale: Double { self == .preparing ? 0.9 : 1 }
170
+ var spin: Angle { self == .sending ? .degrees(360) : .zero }
171
+ var opacity: Double { self == .idle ? 0.6 : 1 }
256
172
  }
257
173
 
258
- struct LoadingIndicator: View {
259
- var body: some View {
260
- PhaseAnimator(LoadPhase.allCases) { phase in
261
- Image(systemName: "arrow.triangle.2.circlepath")
262
- .font(.title)
263
- .scaleEffect(phase.scale)
264
- .rotationEffect(phase.rotation)
265
- .opacity(phase.opacity)
266
- } animation: { phase in
267
- switch phase {
268
- case .ready: .smooth(duration: 0.2)
269
- case .loading: .easeIn(duration: 0.15)
270
- case .spinning: .linear(duration: 0.6)
271
- case .complete: .spring(duration: 0.3, bounce: 0.4)
272
- }
273
- }
174
+ PhaseAnimator(UploadPhase.allCases) { phase in
175
+ Image(systemName: "arrow.up.circle.fill")
176
+ .scaleEffect(phase.scale)
177
+ .rotationEffect(phase.spin)
178
+ .opacity(phase.opacity)
179
+ } animation: { phase in
180
+ switch phase {
181
+ case .idle: .smooth
182
+ case .preparing: .easeIn(duration: 0.2)
183
+ case .sending: .linear(duration: 0.6)
184
+ case .done: .spring(duration: 0.45, bounce: 0.35)
274
185
  }
275
186
  }
276
187
  ```
277
188
 
278
- ### Trigger-Based One-Shot
279
-
280
- Run through all phases once each time the trigger value changes.
189
+ ### One pass per trigger
281
190
 
282
191
  ```swift
283
- struct FeedbackDot: View {
284
- @State private var feedbackTrigger = 0
285
-
286
- var body: some View {
287
- Button { feedbackTrigger += 1 } label: {
288
- Circle()
289
- .frame(width: 20, height: 20)
290
- .phaseAnimator(
291
- [false, true, false],
292
- trigger: feedbackTrigger
293
- ) { content, phase in
294
- content.scaleEffect(phase ? 1.5 : 1.0)
295
- } animation: { _ in
296
- .spring(duration: 0.25, bounce: 0.5)
297
- }
298
- }
299
- .buttonStyle(.plain)
192
+ @State private var taps = 0
193
+
194
+ Button("Like") { taps += 1 }
195
+ .phaseAnimator([false, true, false], trigger: taps) { content, pressed in
196
+ content.scaleEffect(pressed ? 1.2 : 1)
197
+ } animation: { _ in
198
+ .spring(duration: 0.3, bounce: 0.45)
300
199
  }
301
- }
302
200
  ```
303
201
 
304
- ### View Modifier Form
202
+ ### Plain values as phases
203
+
204
+ Phases do not have to be an enum:
305
205
 
306
206
  ```swift
307
- Text("Hello")
308
- .phaseAnimator([0.0, 1.0, 0.0]) { content, phase in
309
- content.opacity(phase)
310
- } animation: { _ in .easeInOut(duration: 0.8) }
207
+ Circle()
208
+ .phaseAnimator([0.2, 1.0, 0.2]) { content, level in
209
+ content.opacity(level)
210
+ } animation: { _ in
211
+ .easeInOut(duration: 0.8)
212
+ }
311
213
  ```
312
214
 
313
- ## KeyframeAnimator Multi-Track Examples
215
+ ## KeyframeAnimator patterns
314
216
 
315
- ### Bounce-and-Fade
217
+ ### Four tracks
316
218
 
317
219
  ```swift
318
- struct BounceValues {
319
- var yOffset: Double = 0
320
- var scale: Double = 1.0
321
- var opacity: Double = 1.0
322
- var rotation: Angle = .zero
220
+ struct DropValues {
221
+ var y = -60.0
222
+ var scale = 0.8
223
+ var tilt = Angle.zero
224
+ var opacity = 0.0
323
225
  }
324
226
 
325
- struct BouncingBadge: View {
326
- @State private var trigger = false
327
-
328
- var body: some View {
329
- Button { trigger.toggle() } label: {
330
- Text("NEW")
331
- .font(.caption.bold())
332
- .padding(.horizontal)
333
- .background(.red, in: Capsule())
334
- .keyframeAnimator(
335
- initialValue: BounceValues(),
336
- trigger: trigger
337
- ) { content, value in
338
- content
339
- .offset(y: value.yOffset)
340
- .scaleEffect(value.scale)
341
- .opacity(value.opacity)
342
- .rotationEffect(value.rotation)
343
- } keyframes: { _ in
344
- KeyframeTrack(\.yOffset) {
345
- SpringKeyframe(-20, duration: 0.2)
346
- CubicKeyframe(5, duration: 0.15)
347
- SpringKeyframe(0, duration: 0.3)
348
- }
349
- KeyframeTrack(\.scale) {
350
- CubicKeyframe(1.3, duration: 0.2)
351
- CubicKeyframe(0.95, duration: 0.15)
352
- SpringKeyframe(1.0, duration: 0.3)
353
- }
354
- KeyframeTrack(\.rotation) {
355
- LinearKeyframe(.degrees(-5), duration: 0.1)
356
- LinearKeyframe(.degrees(5), duration: 0.1)
357
- SpringKeyframe(.zero, duration: 0.2)
358
- }
359
- KeyframeTrack(\.opacity) {
360
- MoveKeyframe(1.0)
361
- }
362
- }
227
+ Image(systemName: "gift.fill")
228
+ .keyframeAnimator(initialValue: DropValues(), trigger: revealCount) { view, v in
229
+ view
230
+ .offset(y: v.y)
231
+ .scaleEffect(v.scale)
232
+ .rotationEffect(v.tilt)
233
+ .opacity(v.opacity)
234
+ } keyframes: { _ in
235
+ KeyframeTrack(\.y) {
236
+ CubicKeyframe(0, duration: 0.3)
237
+ SpringKeyframe(-12, duration: 0.15)
238
+ SpringKeyframe(0, duration: 0.25)
239
+ }
240
+ KeyframeTrack(\.scale) {
241
+ CubicKeyframe(1.1, duration: 0.3)
242
+ SpringKeyframe(1.0, duration: 0.4)
243
+ }
244
+ KeyframeTrack(\.tilt) {
245
+ LinearKeyframe(.degrees(-8), duration: 0.15)
246
+ LinearKeyframe(.degrees(8), duration: 0.15)
247
+ SpringKeyframe(.zero, duration: 0.3)
248
+ }
249
+ KeyframeTrack(\.opacity) {
250
+ MoveKeyframe(1.0)
363
251
  }
364
- .buttonStyle(.plain)
365
252
  }
366
- }
367
253
  ```
368
254
 
369
- ### Repeating Keyframe Animation
255
+ ### Looping
370
256
 
371
257
  ```swift
372
- KeyframeAnimator(
373
- initialValue: PulseValues(),
374
- repeating: true
375
- ) { value in
258
+ struct Glow {
259
+ var scale = 1.0
260
+ var opacity = 0.5
261
+ }
262
+
263
+ KeyframeAnimator(initialValue: Glow(), repeating: true) { glow in
376
264
  Circle()
377
- .fill(.blue)
378
- .frame(width: 40, height: 40)
379
- .scaleEffect(value.scale)
380
- .opacity(value.opacity)
265
+ .fill(.yellow)
266
+ .scaleEffect(glow.scale)
267
+ .opacity(glow.opacity)
381
268
  } keyframes: { _ in
382
269
  KeyframeTrack(\.scale) {
383
- CubicKeyframe(1.3, duration: 0.5)
384
- CubicKeyframe(1.0, duration: 0.5)
270
+ CubicKeyframe(1.15, duration: 0.6)
271
+ CubicKeyframe(1, duration: 0.6)
385
272
  }
386
273
  KeyframeTrack(\.opacity) {
387
- CubicKeyframe(0.6, duration: 0.5)
388
- CubicKeyframe(1.0, duration: 0.5)
274
+ CubicKeyframe(0.95, duration: 0.6)
275
+ CubicKeyframe(0.5, duration: 0.6)
389
276
  }
390
277
  }
391
278
  ```
392
279
 
393
- ### Keyframe Type Reference
280
+ ### Keyframe types
394
281
 
395
- | Type | Interpolation | Use case |
282
+ | Keyframe | Motion | Typical use |
396
283
  |---|---|---|
397
- | `LinearKeyframe(value, duration:)` | Straight line between values | Steady movement |
398
- | `CubicKeyframe(value, duration:)` | Cubic bezier curve | Smooth easing |
399
- | `SpringKeyframe(value, duration:, spring:)` | Spring physics | Natural settle |
400
- | `MoveKeyframe(value)` | Instant jump | Reset to value immediately |
284
+ | `LinearKeyframe(value, duration:)` | straight-line interpolation | steady movement |
285
+ | `CubicKeyframe(value, duration:)` | cubic Bezier | smooth easing |
286
+ | `SpringKeyframe(value, duration:spring:)` | driven by a spring | settling into place |
287
+ | `MoveKeyframe(value)` | instant jump | resetting a value |
401
288
 
402
- ### KeyframeTimeline for Manual Evaluation
289
+ ### Evaluating keyframes yourself
403
290
 
404
291
  ```swift
405
- let timeline = KeyframeTimeline(initialValue: AnimValues()) {
406
- KeyframeTrack(\.scale) {
407
- CubicKeyframe(1.5, duration: 0.3)
408
- CubicKeyframe(1.0, duration: 0.4)
409
- }
292
+ let timeline = KeyframeTimeline(initialValue: 0.0) {
293
+ CubicKeyframe(20, duration: 0.3)
294
+ SpringKeyframe(0, duration: 0.5)
410
295
  }
411
-
412
- let totalDuration = timeline.duration
413
- let valueAtHalf = timeline.value(time: totalDuration / 2)
296
+ let total = timeline.duration // 0.8
297
+ let atHalf = timeline.value(time: 0.5)
414
298
  ```
415
299
 
416
300
  ## Transaction and TransactionKey
417
301
 
418
- ### Transaction Basics
419
-
420
- A `Transaction` carries the animation context for a state change. Every
421
- `withAnimation` call creates a transaction internally.
302
+ Every state change travels with a `Transaction` that says how to animate it;
303
+ `withAnimation` creates one behind the scenes.
422
304
 
423
305
  ```swift
424
- // Explicit transaction
425
- var transaction = Transaction(animation: .spring)
426
- withTransaction(transaction) {
427
- isExpanded = true
428
- }
306
+ let pinning = Transaction(animation: .snappy)
307
+ withTransaction(pinning) { isPinned = true }
429
308
  ```
430
309
 
431
- ### Overriding Animations with Transaction
310
+ Removing the incoming animation for one subtree:
432
311
 
433
312
  ```swift
434
- // Remove the incoming transaction animation for this scoped content
435
- SomeView()
436
- .transaction { transaction in
437
- transaction.animation = nil
438
- }
439
-
440
- // Override the scoped transaction animation when a value changes
441
- SomeView()
442
- .transaction(value: selectedTab) { transaction in
443
- transaction.animation = .smooth(duration: 0.3)
444
- }
313
+ BadgeView(count: count)
314
+ .transaction { $0.animation = nil }
445
315
  ```
446
316
 
447
- ### Custom TransactionKey
317
+ Overriding only when a particular value changes:
318
+
319
+ ```swift
320
+ ChartView(points: points)
321
+ .transaction(value: selectedRange) { $0.animation = .smooth }
322
+ ```
448
323
 
449
- Store custom metadata in transactions.
324
+ ### Custom transaction metadata
450
325
 
451
326
  ```swift
452
- struct IsInteractiveKey: TransactionKey {
327
+ struct DragDrivenKey: TransactionKey {
453
328
  static let defaultValue = false
454
329
  }
455
330
 
456
331
  extension Transaction {
457
- var isInteractive: Bool {
458
- get { self[IsInteractiveKey.self] }
459
- set { self[IsInteractiveKey.self] = newValue }
332
+ var isDragDriven: Bool {
333
+ get { self[DragDrivenKey.self] }
334
+ set { self[DragDrivenKey.self] = newValue }
460
335
  }
461
336
  }
462
337
 
463
- // Usage
338
+ // During a drag:
464
339
  var transaction = Transaction(animation: .interactiveSpring)
465
- transaction.isInteractive = true
466
- withTransaction(transaction) { dragOffset = newOffset }
340
+ transaction.isDragDriven = true
341
+ withTransaction(transaction) { cardOffset = dragTranslation }
467
342
  ```
468
343
 
469
- ### Scoped Transaction Override
344
+ A view downstream can read `transaction.isDragDriven` inside
345
+ `.transaction { }` and choose a different animation.
346
+
347
+ ### Scoped override
348
+
349
+ `.transaction(_:body:)` applies the change only to the modifiers in its body:
470
350
 
471
351
  ```swift
472
- // Apply transaction only within a body closure
473
- ParentView()
474
- .transaction { $0.animation = .spring } body: { content in
475
- content.scaleEffect(scale)
352
+ Card()
353
+ .transaction { t in t.animation = .bouncy } body: { view in
354
+ view.scaleEffect(zoom)
476
355
  }
477
356
  ```
478
357
 
479
- ### Scoped Implicit Animation
358
+ ## Choosing an implicit scope
480
359
 
481
- Use `.animation(_:body:)` when only selected modifiers should animate.
482
- Use `.animation(_:value:)` when a single value change should drive the view's
483
- animatable modifiers together. Use `.transaction(_:body:)` when you need to
484
- scope transaction overrides rather than attach one animation.
360
+ | Need | Use |
361
+ |---|---|
362
+ | Animate some modifiers, not others | `.animation(_:body:)` |
363
+ | One value drives every animatable modifier | `.animation(_:value:)` |
364
+ | Adjust the transaction for a region rather than attach one animation | `.transaction(_:body:)` |
485
365
 
486
366
  ```swift
487
- CardView(isExpanded: isExpanded)
367
+ NoteCard(note: note)
488
368
  .animation(.smooth) { content in
489
369
  content
490
- .scaleEffect(isExpanded ? 1.05 : 1.0)
491
- .shadow(radius: isExpanded ? 12 : 4)
370
+ .scaleEffect(isExpanded ? 1.05 : 1)
371
+ .shadow(radius: isExpanded ? 12 : 2)
492
372
  }
493
373
  ```
494
374
 
495
- ## All Transition Types (iOS 17+)
496
-
497
- ### Built-in Transitions
375
+ ## Every transition (iOS 17+)
498
376
 
499
- | Transition | Description | Example |
500
- |---|---|---|
501
- | `.opacity` | Fade in/out | `.transition(.opacity)` |
502
- | `.slide` | Slide from leading, exit trailing | `.transition(.slide)` |
503
- | `.scale` | Scale from zero | `.transition(.scale)` |
504
- | `.scale(_:anchor:)` | Scale with amount and anchor | `.transition(.scale(0.5, anchor: .bottom))` |
505
- | `.move(edge:)` | Move from specified edge | `.transition(.move(edge: .top))` |
506
- | `.push(from:)` | Push from edge with fade | `.transition(.push(from: .trailing))` |
507
- | `.offset(_:)` | Offset by CGSize | `.transition(.offset(CGSize(width: 0, height: 50)))` |
508
- | `.offset(x:y:)` | Offset by x and y | `.transition(.offset(x: 0, y: -100))` |
509
- | `.identity` | No visual change | `.transition(.identity)` |
510
- | `.blurReplace` | Blur and scale combined | `.transition(.blurReplace)` |
511
- | `.blurReplace(_:)` | Configurable blur replace | `.transition(.blurReplace(.downUp))` |
512
- | `.symbolEffect` | Default symbol effect | `.transition(.symbolEffect)` |
513
- | `.symbolEffect(_:options:)` | Custom symbol effect | `.transition(.symbolEffect(.appear))` |
514
-
515
- ### Combining Transitions
377
+ | Transition | Effect |
378
+ |---|---|
379
+ | `.opacity` | fade |
380
+ | `.slide` | in from the leading edge, out toward the trailing edge |
381
+ | `.scale` | grows from zero |
382
+ | `.scale(0.5, anchor: .bottom)` | grows from a size and anchor |
383
+ | `.move(edge: .leading)` | slides from an edge |
384
+ | `.push(from: .trailing)` | push with a fade from an edge |
385
+ | `.offset(CGSize(width: 0, height: 40))` | shifts by a size |
386
+ | `.offset(x: 0, y: 40)` | shifts by components |
387
+ | `.identity` | no visual change |
388
+ | `.blurReplace` | blur combined with scale |
389
+ | `.blurReplace(.downUp)` | configurable blur replace |
390
+ | `.symbolEffect` | the default symbol transition |
391
+ | `.symbolEffect(.appear)` | a chosen symbol effect, with options |
392
+
393
+ Combining and splitting:
516
394
 
517
395
  ```swift
518
- // Slide + fade
519
396
  .transition(.slide.combined(with: .opacity))
520
-
521
- // Move from top + scale
522
- .transition(.move(edge: .top).combined(with: .scale))
523
- ```
524
-
525
- ### Asymmetric Transitions
526
-
527
- Different animation for insertion vs removal.
528
-
529
- ```swift
397
+ .transition(.scale.combined(with: .move(edge: .leading)))
530
398
  .transition(.asymmetric(
531
- insertion: .push(from: .bottom).combined(with: .opacity),
532
- removal: .scale.combined(with: .opacity)
399
+ insertion: .opacity.combined(with: .push(from: .top)),
400
+ removal: .opacity.combined(with: .scale(scale: 0.8))
533
401
  ))
534
402
  ```
535
403
 
536
- ### Custom Transition
404
+ `.asymmetric(insertion:removal:)` exists only on `AnyTransition`, so every
405
+ argument inside it resolves to the `AnyTransition` members, where the sized
406
+ scale is spelled `.scale(scale:anchor:)`. Elsewhere the `Transition` form
407
+ `.scale(0.8)` works.
408
+
409
+ ### A custom Transition
537
410
 
538
411
  ```swift
539
- struct RotateTransition: Transition {
412
+ struct FlipIn: Transition {
540
413
  func body(content: Content, phase: TransitionPhase) -> some View {
541
414
  content
542
- .rotationEffect(phase.isIdentity ? .zero : .degrees(90))
415
+ .rotation3DEffect(.degrees(phase.isIdentity ? 0 : 90), axis: (x: 1, y: 0, z: 0))
543
416
  .opacity(phase.isIdentity ? 1 : 0)
544
417
  }
545
418
  }
546
419
 
547
- extension AnyTransition {
548
- static var rotate: AnyTransition {
549
- .init(RotateTransition())
550
- }
420
+ extension Transition where Self == FlipIn {
421
+ static var flipIn: FlipIn { FlipIn() }
551
422
  }
423
+
424
+ // .transition(.flipIn)
552
425
  ```
553
426
 
554
- ### TransitionPhase
427
+ Where an API still wants the older type-erased value, wrap it:
428
+ `AnyTransition(FlipIn())`, for example behind
429
+ `extension AnyTransition { static var flipIn: AnyTransition { AnyTransition(FlipIn()) } }`.
555
430
 
556
- ```swift
557
- enum TransitionPhase {
558
- case willAppear // View is about to be inserted
559
- case identity // View is fully presented
560
- case didDisappear // View is being removed
561
- }
431
+ `TransitionPhase`:
562
432
 
563
- // Check current phase
564
- phase.isIdentity // true when fully presented
565
- ```
433
+ - `.willAppear`: about to be inserted.
434
+ - `.identity`: fully on screen.
435
+ - `.didDisappear`: being removed.
436
+ - `isIdentity`: true only in the fully presented phase.
566
437
 
567
- ### Attaching Animation to Transition
438
+ ### Pinning an animation to a transition
568
439
 
569
440
  ```swift
570
441
  .transition(
571
- .move(edge: .bottom)
442
+ .scale(0.9)
572
443
  .combined(with: .opacity)
573
- .animation(.spring(duration: 0.4, bounce: 0.2))
444
+ .animation(.spring(duration: 0.3, bounce: 0.15))
574
445
  )
575
446
  ```
576
447
 
577
- ## All Symbol Effect Types
578
-
579
- ### Discrete Effects (trigger with `value:`)
580
-
581
- | Effect | Scope | Direction |
582
- |---|---|---|
583
- | `.bounce` | `.byLayer`, `.wholeSymbol` | -- |
584
- | `.wiggle` | `.byLayer`, `.wholeSymbol` | `.up`, `.down`, `.left`, `.right`, `.forward`, `.backward`, `.clockwise`, `.counterClockwise`, `.custom(angle:)` |
585
-
586
- ```swift
587
- Image(systemName: "bell.fill")
588
- .symbolEffect(.bounce.byLayer, value: count)
589
-
590
- Image(systemName: "arrow.left.arrow.right")
591
- .symbolEffect(.wiggle.left, value: swapCount)
592
- ```
593
-
594
- ### Indefinite Effects (toggle with `isActive:`)
595
-
596
- | Effect | Scope | Direction |
597
- |---|---|---|
598
- | `.pulse` | `.byLayer`, `.wholeSymbol` | -- |
599
- | `.variableColor` | `.byLayer`, `.wholeSymbol` | Chaining: `.cumulative`/`.iterative`, `.reversing`/`.nonReversing`, `.dimInactiveLayers`/`.hideInactiveLayers` |
600
- | `.scale` | `.byLayer`, `.wholeSymbol` | `.up`, `.down` |
601
- | `.breathe` | `.byLayer`, `.wholeSymbol` | -- |
602
- | `.rotate` | `.byLayer`, `.wholeSymbol` | `.clockwise`, `.counterClockwise` |
603
-
604
- ```swift
605
- Image(systemName: "wifi")
606
- .symbolEffect(.pulse.byLayer, isActive: isConnecting)
607
-
608
- Image(systemName: "gear")
609
- .symbolEffect(.rotate.clockwise, isActive: isProcessing)
610
-
611
- Image(systemName: "speaker.wave.3.fill")
612
- .symbolEffect(
613
- .variableColor.cumulative.nonReversing.dimInactiveLayers,
614
- options: .repeating,
615
- isActive: isPlaying
616
- )
617
-
618
- Image(systemName: "magnifyingglass")
619
- .symbolEffect(.scale.up, isActive: isHighlighted)
620
-
621
- Image(systemName: "heart.fill")
622
- .symbolEffect(.breathe, isActive: isFavorite)
623
- ```
624
-
625
- ### Transition Effects (appear/disappear)
626
-
627
- ```swift
628
- Image(systemName: "checkmark.circle.fill")
629
- .symbolEffect(.appear, isActive: showCheck)
630
-
631
- Image(systemName: "xmark.circle")
632
- .symbolEffect(.disappear, isActive: shouldHide)
633
- ```
634
-
635
- ### Content Transition Effects (replace)
448
+ ## Every symbol effect
636
449
 
637
- ```swift
638
- Image(systemName: isMuted ? "speaker.slash" : "speaker.wave.3")
639
- .contentTransition(.symbolEffect(.replace.downUp))
450
+ ### Discrete, triggered by `value:`
640
451
 
641
- // Magic replace (morphs between symbols)
642
- Image(systemName: isPlaying ? "pause.fill" : "play.fill")
643
- .contentTransition(.symbolEffect(.replace.magic(fallback: .downUp)))
644
- ```
645
-
646
- Replace directions: `.downUp`, `.offUp`, `.upUp`.
647
-
648
- ### SymbolEffectOptions
452
+ - `.bounce`: scopes `.byLayer` and `.wholeSymbol`; no direction.
453
+ - `.wiggle` (iOS 18): scopes, plus directions `.up`, `.down`, `.left`,
454
+ `.right`, `.forward`, `.backward`, `.clockwise`, `.counterClockwise`,
455
+ `.custom(angle:)`.
649
456
 
650
457
  ```swift
651
- .symbolEffect(.pulse, options: .default, isActive: true)
652
- .symbolEffect(.bounce, options: .repeating, value: count)
653
- .symbolEffect(.pulse, options: .nonRepeating, isActive: true)
654
- .symbolEffect(.bounce, options: .repeat(3), value: count)
655
- .symbolEffect(.pulse, options: .speed(2.0), isActive: true)
656
-
657
- // RepeatBehavior
658
- .symbolEffect(.bounce, options: .repeat(.periodic(3, delay: 0.5)), value: count)
659
- .symbolEffect(.pulse, options: .repeat(.continuous), isActive: true)
458
+ Image(systemName: "tray.and.arrow.down.fill").symbolEffect(.bounce.byLayer, value: downloads)
459
+ Image(systemName: "hand.wave.fill").symbolEffect(.wiggle.left, value: greetings)
660
460
  ```
661
461
 
662
- ### Removing Effects
663
-
664
- ```swift
665
- Image(systemName: "star.fill")
666
- .symbolEffect(.pulse, isActive: true)
667
- .symbolEffectsRemoved(reduceMotion)
668
- ```
462
+ ### Indefinite, controlled by `isActive:`
669
463
 
670
- ## Reduce Motion Implementation Patterns
464
+ - `.pulse`
465
+ - `.variableColor`: chain `.cumulative` or `.iterative`, `.reversing` or
466
+ `.nonReversing`, `.dimInactiveLayers` or `.hideInactiveLayers`.
467
+ - `.scale`: `.up` or `.down`.
468
+ - `.breathe` (iOS 18)
469
+ - `.rotate` (iOS 18): `.clockwise` or `.counterClockwise`.
671
470
 
672
- ### Environment Variable
471
+ All of them take `.byLayer` or `.wholeSymbol`.
673
472
 
674
473
  ```swift
675
- @Environment(\.accessibilityReduceMotion) private var reduceMotion
474
+ Image(systemName: "antenna.radiowaves.left.and.right").symbolEffect(.pulse.byLayer, isActive: isLive)
475
+ Image(systemName: "gearshape.fill").symbolEffect(.rotate.clockwise, isActive: isSyncing)
476
+ Image(systemName: "chart.bar.fill")
477
+ .symbolEffect(.variableColor.cumulative.nonReversing.dimInactiveLayers,
478
+ options: .repeating, isActive: isLoading)
479
+ Image(systemName: "plus.circle").symbolEffect(.scale.up, isActive: isHighlighted)
480
+ Image(systemName: "moon.zzz.fill").symbolEffect(.breathe, isActive: isSleeping)
676
481
  ```
677
482
 
678
- ### Pattern 1: Conditional Animation
483
+ ### Appear and disappear
679
484
 
680
485
  ```swift
681
- withAnimation(reduceMotion ? .none : .bouncy) {
682
- isExpanded.toggle()
683
- }
486
+ Image(systemName: "checkmark.seal.fill").symbolEffect(.appear, isActive: isVerified)
487
+ Image(systemName: "exclamationmark.triangle").symbolEffect(.disappear, isActive: isResolved)
684
488
  ```
685
489
 
686
- ### Pattern 2: Simplified Animation
687
-
688
- Replace bouncy/spring with crossfade when reduce motion is on.
689
-
690
- ```swift
691
- withAnimation(reduceMotion ? .easeInOut(duration: 0.2) : .spring(duration: 0.4, bounce: 0.3)) {
692
- selectedTab = newTab
693
- }
694
- ```
490
+ ### Replace
695
491
 
696
- ### Pattern 3: Disable Repeating Animations
492
+ Replacement runs as a content transition:
697
493
 
698
494
  ```swift
699
- // WRONG: Ignores reduce motion
700
- PhaseAnimator(phases) { phase in /* ... */ }
701
-
702
- // CORRECT: Use trigger-based or skip entirely
703
- if !reduceMotion {
704
- PhaseAnimator(phases) { phase in /* ... */ }
705
- } else {
706
- StaticView()
707
- }
708
- ```
709
-
710
- ### Pattern 4: Symbol Effects
711
-
712
- ```swift
713
- Image(systemName: "wifi")
714
- .symbolEffect(.pulse, isActive: isSearching)
715
- .symbolEffectsRemoved(reduceMotion)
716
- ```
717
-
718
- ### Pattern 5: Reusable Helper
719
-
720
- ```swift
721
- extension Animation {
722
- static func adaptive(
723
- _ animation: Animation,
724
- reduceMotion: Bool
725
- ) -> Animation? {
726
- reduceMotion ? nil : animation
727
- }
728
- }
729
-
730
- // Usage
731
- withAnimation(.adaptive(.bouncy, reduceMotion: reduceMotion)) {
732
- isVisible = true
733
- }
734
- ```
735
-
736
- ## Animation Performance Tips
737
-
738
- ### Keep Content Closures Light
739
-
740
- The `content` closure in `KeyframeAnimator` and `PhaseAnimator` runs every
741
- frame while animating. Keep it to simple view modifiers.
742
-
743
- ```swift
744
- // WRONG: Expensive computation per frame
745
- .keyframeAnimator(initialValue: v, trigger: t) { content, value in
746
- let result = heavyComputation(value.progress)
747
- return content.opacity(result)
748
- } keyframes: { _ in /* ... */ }
749
-
750
- // CORRECT: Only apply view modifiers
751
- .keyframeAnimator(initialValue: v, trigger: t) { content, value in
752
- content.opacity(value.opacity)
753
- } keyframes: { _ in /* ... */ }
754
- ```
755
-
756
- ### Prefer Modifier-Based Animations
757
-
758
- Animating view modifiers (`opacity`, `scaleEffect`, `offset`, `rotationEffect`)
759
- is highly optimized. Avoid animating layout-triggering properties when possible.
760
-
761
- ### Use drawingGroup for Complex Compositing
762
-
763
- ```swift
764
- ComplexAnimatedView()
765
- .drawingGroup()
766
- ```
767
-
768
- Flattens the view hierarchy into a single Metal-backed layer. Use when
769
- compositing many overlapping animated views.
770
-
771
- ### Limit Concurrent Animations
772
-
773
- Avoid animating dozens of views simultaneously. Use staggered delays.
774
-
775
- ```swift
776
- ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
777
- ItemView(item: item)
778
- .transition(.move(edge: .bottom).combined(with: .opacity))
779
- .animation(.spring.delay(Double(index) * 0.05), value: isVisible)
780
- }
781
- ```
782
-
783
- ### Avoid Re-creating Views During Animation
784
-
785
- Ensure animated views maintain stable identity. Use explicit `id()` modifiers
786
- or stable `ForEach` identifiers.
787
-
788
- ```swift
789
- // WRONG: View identity changes, breaks animation
790
- ForEach(Array(items.enumerated()), id: \.offset) { index, item in
791
- ItemView(item: item)
792
- }
495
+ Image(systemName: isLocked ? "lock.fill" : "lock.open.fill")
496
+ .contentTransition(.symbolEffect(.replace.downUp))
793
497
 
794
- // CORRECT: Stable identity from model
795
- ForEach(items) { item in
796
- ItemView(item: item)
797
- }
498
+ Image(systemName: isRecording ? "stop.circle.fill" : "record.circle")
499
+ .contentTransition(.symbolEffect(.replace.magic(fallback: .offUp))) // iOS 18
798
500
  ```
799
501
 
800
- ### Use geometryGroup() for Nested Geometry
502
+ Directions: `.downUp`, `.offUp`, `.upUp`. `.magic` morphs shared parts of the
503
+ two symbols and falls back to the given direction when it cannot.
801
504
 
802
- Isolate child geometry from parent animations when they conflict.
505
+ ### Options
803
506
 
804
507
  ```swift
805
- ParentView()
806
- .scaleEffect(parentScale)
807
- .geometryGroup() // children see stable geometry
508
+ .symbolEffect(.bounce, options: .default, value: n)
509
+ .symbolEffect(.bounce, options: .repeating, value: n)
510
+ .symbolEffect(.bounce, options: .nonRepeating, value: n)
511
+ .symbolEffect(.bounce, options: .repeat(2), value: n)
512
+ .symbolEffect(.bounce, options: .speed(2.0), value: n)
513
+ .symbolEffect(.breathe, options: .repeat(.periodic(4, delay: 1.0)), isActive: on) // iOS 18
514
+ .symbolEffect(.breathe, options: .repeat(.continuous), isActive: on) // iOS 18
808
515
  ```
809
516
 
810
- ### Transaction for Selective Animation Override
517
+ `.symbolEffectsRemoved(_:)` strips symbol effects from a subtree, for example
518
+ `.symbolEffectsRemoved(reduceMotion)`.
811
519
 
812
- Override animation for specific subtrees without affecting siblings.
520
+ ## Reduce Motion
813
521
 
814
522
  ```swift
815
- // Disable animation on one child while parent animates
816
- ChildView()
817
- .transaction { $0.animation = nil }
523
+ @Environment(\.accessibilityReduceMotion) private var reduceMotion
818
524
  ```
819
525
 
820
- ### Profile with Instruments
821
-
822
- Use the Core Animation instrument in Xcode Instruments to verify:
823
- - Frame rate stays at 120 fps (ProMotion devices) or 60 fps.
824
- - No offscreen rendering passes.
825
- - GPU utilization stays reasonable during animations.
526
+ 1. **No animation.**
527
+
528
+ ```swift
529
+ withAnimation(reduceMotion ? .none : .bouncy) { isOpen.toggle() }
530
+ ```
531
+
532
+ 2. **A calmer substitute.** Swap a spring or bounce for a short fade-like
533
+ curve:
534
+
535
+ ```swift
536
+ .animation(reduceMotion ? .easeInOut(duration: 0.2) : .bouncy, value: isOpen)
537
+ ```
538
+
539
+ 3. **No endless loops.** A `PhaseAnimator` without a trigger keeps moving no
540
+ matter what the setting says.
541
+
542
+ ```swift
543
+ if reduceMotion {
544
+ LoadingDots()
545
+ } else {
546
+ PhaseAnimator([0.4, 1.0]) { level in
547
+ LoadingDots().opacity(level)
548
+ }
549
+ }
550
+ ```
551
+
552
+ The trigger form, which plays once per change, is the other option.
553
+
554
+ 4. **Symbol effects.**
555
+
556
+ ```swift
557
+ StatusIcons().symbolEffectsRemoved(reduceMotion)
558
+ ```
559
+
560
+ 5. **A helper.**
561
+
562
+ ```swift
563
+ extension Animation {
564
+ static func motionAware(_ preferred: Animation, reduceMotion: Bool) -> Animation? {
565
+ reduceMotion ? nil : preferred
566
+ }
567
+ }
568
+
569
+ withAnimation(.motionAware(.bouncy, reduceMotion: reduceMotion)) { isOpen.toggle() }
570
+ ```
571
+
572
+ ## Performance
573
+
574
+ - **Per-frame closures stay light.** `KeyframeAnimator` and `PhaseAnimator`
575
+ call their content every frame.
576
+
577
+ ```swift
578
+ // Wrong: sorts on every frame
579
+ .keyframeAnimator(initialValue: 0.0, trigger: t) { view, v in
580
+ view.overlay(Text(scores.sorted().first ?? "")).opacity(v)
581
+ } keyframes: { _ in LinearKeyframe(1.0, duration: 0.3) }
582
+
583
+ // Right: computed once, read per frame
584
+ let leader = scores.sorted().first ?? ""
585
+ // ... view.overlay(Text(leader)).opacity(v)
586
+ ```
587
+
588
+ - **Animate cheap properties.** `opacity`, `scaleEffect`, `offset` and
589
+ `rotationEffect` are handled efficiently by the renderer. Animating `frame`,
590
+ padding or anything else that forces layout costs more.
591
+ - **`.drawingGroup()`** flattens a subtree and draws it offscreen with Metal as
592
+ one image; useful when many overlapping views animate together.
593
+ - **Stagger large groups** instead of starting dozens of animations at once:
594
+
595
+ ```swift
596
+ ForEach(Array(cards.enumerated()), id: \.element.id) { index, card in
597
+ CardView(card: card)
598
+ .offset(y: isShown ? 0 : 30)
599
+ .opacity(isShown ? 1 : 0)
600
+ .animation(.snappy.delay(Double(index) * 0.04), value: isShown)
601
+ }
602
+ ```
603
+
604
+ - **Stable identity.** `ForEach(items.enumerated(), id: \.offset)` ties
605
+ identity to position, so inserts and deletes animate the wrong views. Use
606
+ the model's identity (`ForEach(items)`) or an explicit `.id()`.
607
+ - **`.geometryGroup()`** makes children resolve their geometry together, which
608
+ stops them fighting a parent's animation such as a `scaleEffect`.
609
+ - **Opt a child out** with `.transaction { $0.animation = nil }` while its
610
+ parent and siblings keep animating.
611
+ - **Measure** with the Core Animation instrument in Instruments: aim for
612
+ 120 fps on ProMotion displays (60 elsewhere), no offscreen rendering passes,
613
+ and moderate GPU load.