@mmerterden/multi-agent-pipeline 20.6.0 → 20.8.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 (326) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +328 -329
  5. package/package.json +2 -2
  6. package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
  7. package/pipeline/scripts/_notices.mjs +12 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  9. package/pipeline/skills/.skill-manifest.json +106 -106
  10. package/pipeline/skills/shared/README.md +71 -71
  11. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
  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/android-architecture/SKILL.md +2 -0
  16. package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
  17. package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
  18. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  19. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  20. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  21. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  22. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  23. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  24. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  25. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  26. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  27. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  28. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  29. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  30. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  31. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  32. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  33. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  34. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  35. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  37. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  38. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  39. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  40. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  41. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  42. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  43. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  44. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  45. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  46. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  47. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  48. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  49. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  50. package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
  51. package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
  52. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  53. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  54. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  55. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
  56. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  57. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  58. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  59. package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
  60. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
  61. package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
  62. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  63. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  64. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  65. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  66. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  67. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  68. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  69. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  70. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  71. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  72. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  73. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  74. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  75. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  76. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  77. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  78. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  79. package/pipeline/skills/shared/external/council/SKILL.md +1 -0
  80. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  81. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  82. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  83. package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
  84. package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
  85. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  86. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  87. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  88. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  89. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  90. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  91. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  92. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  93. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  94. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  95. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  96. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  97. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  98. package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
  99. package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
  100. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
  101. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  102. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  103. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  104. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  105. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  106. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  107. package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
  108. package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
  109. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  110. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  111. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  112. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  113. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  114. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
  115. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  116. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  117. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  118. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  119. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
  120. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  121. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  122. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  123. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  124. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  125. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  126. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  127. package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
  128. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  129. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  130. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  131. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  132. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  133. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  134. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
  135. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
  136. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
  137. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
  138. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
  139. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
  140. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
  141. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
  142. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
  143. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
  144. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
  145. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
  146. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
  147. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
  148. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
  149. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
  150. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  151. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  152. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  153. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  154. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  155. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  156. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  157. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  158. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  159. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  160. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  161. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  162. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  163. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
  164. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  165. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  166. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  167. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  168. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  169. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  170. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  171. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  172. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  173. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  174. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  175. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  176. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  177. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  178. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  179. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  180. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  181. package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
  182. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  183. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  184. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  185. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  186. package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
  187. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
  188. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  189. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  190. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  191. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
  192. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
  193. package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
  194. package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
  195. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  196. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  197. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  198. package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
  199. package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
  200. package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
  201. package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
  202. package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
  203. package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
  204. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
  205. package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
  206. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  207. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  208. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  209. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  210. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  211. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  212. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  213. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  214. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  215. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  216. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  217. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  218. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  219. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  220. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  221. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  222. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  223. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  224. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  225. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  226. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  227. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  228. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  229. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  230. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  231. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  232. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  233. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  234. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  235. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  236. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  237. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  238. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  239. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  240. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  241. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  242. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  243. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  244. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  245. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  246. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  247. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  248. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  249. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  250. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  251. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  252. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  253. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  254. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  255. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  256. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  257. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  258. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  259. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  260. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  261. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  262. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  263. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  264. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  265. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  266. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  267. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  268. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  269. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  270. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  271. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  272. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  273. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  274. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  275. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  276. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  277. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  278. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  279. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  280. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  281. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  282. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  283. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  284. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  285. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  286. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  287. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  288. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  289. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  290. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  291. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  292. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  293. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  294. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  295. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  296. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  297. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  298. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  299. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  300. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  301. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  302. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  303. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  304. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  305. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  306. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  307. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  308. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  309. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  310. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
  311. package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
  312. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  313. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  314. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
  315. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  316. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  317. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  318. package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
  319. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  320. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  321. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
  322. package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
  323. package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
  324. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  325. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  326. 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.