@mmerterden/multi-agent-pipeline 20.7.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 (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,553 +1,504 @@
1
1
  # Core Animation Bridge
2
2
 
3
- Patterns for bridging Core Animation (QuartzCore) with SwiftUI. Use when SwiftUI's built-in animation system is insufficient -- typically for performance-critical layer animations, unsupported animation curves, or direct CALayer manipulation. Overflow reference for the `swiftui-animation` skill.
3
+ Material that did not fit in the main SwiftUI animation skill. Reach for Core
4
+ Animation only when SwiftUI's animation system cannot do the job: layer animation on a
5
+ hot path, a curve SwiftUI cannot express, or direct work on a `CALayer`.
4
6
 
5
7
  ## Contents
6
8
 
7
- - [When to Drop Below SwiftUI Animations](#when-to-drop-below-swiftui-animations)
9
+ - [When dropping down is justified](#when-dropping-down-is-justified)
10
+ - [The model value rule](#the-model-value-rule)
8
11
  - [CABasicAnimation](#cabasicanimation)
9
12
  - [CAKeyframeAnimation](#cakeyframeanimation)
10
13
  - [CASpringAnimation](#caspringanimation)
11
14
  - [CAAnimationGroup](#caanimationgroup)
12
15
  - [CADisplayLink](#cadisplaylink)
13
- - [UIViewRepresentable Wrapper for CA Layers](#uiviewrepresentable-wrapper-for-ca-layers)
14
- - [Bridging CA Animations with SwiftUI State](#bridging-ca-animations-with-swiftui-state)
15
- - [Performance Considerations](#performance-considerations)
16
-
17
- ## When to Drop Below SwiftUI Animations
18
-
19
- SwiftUI's animation system covers most use cases. Drop to Core Animation only when:
20
-
21
- | Scenario | Why CA Is Needed |
22
- |----------|-----------------|
23
- | **Custom timing functions** beyond spring/ease | `CAMediaTimingFunction` supports arbitrary cubic Bezier curves |
24
- | **Layer-specific properties** (shadowPath, borderWidth, etc.) | SwiftUI does not expose all CALayer animatable properties |
25
- | **Additive animations** | CA supports additive blending of multiple concurrent animations on the same property |
26
- | **Frame-synchronized drawing** | `CADisplayLink` provides precise frame timing for custom rendering |
27
- | **Performance-critical particle/effects** | Direct layer manipulation avoids SwiftUI's diffing overhead |
28
- | **Animation along a path** | `CAKeyframeAnimation` supports `CGPath`-based animation paths |
29
-
30
- If SwiftUI's `withAnimation`, `PhaseAnimator`, or `KeyframeAnimator` can achieve the effect, prefer them. Core Animation bridging adds complexity and requires explicit `UIViewRepresentable` wrappers.
16
+ - [Hosting layer animation in SwiftUI](#hosting-layer-animation-in-swiftui)
17
+ - [Moving state across the bridge](#moving-state-across-the-bridge)
18
+ - [Performance](#performance)
19
+ - [Apple documentation](#apple-documentation)
20
+
21
+ ## When dropping down is justified
22
+
23
+ | Need | Why Core Animation |
24
+ |---|---|
25
+ | Arbitrary timing | `CAMediaTimingFunction` takes any cubic Bezier control points |
26
+ | Layer-only properties | `shadowPath`, `borderWidth` and similar are not all reachable from SwiftUI |
27
+ | Additive animation | several animations can run on one property at the same time and blend |
28
+ | Drawing locked to the display | `CADisplayLink` fires once per refresh |
29
+ | Particles and ambient effects | plain layers skip SwiftUI's view diffing entirely |
30
+ | Motion along a path | `CAKeyframeAnimation` follows a `CGPath` |
31
+
32
+ If `withAnimation`, `PhaseAnimator` or `KeyframeAnimator` can do it, use them.
33
+ Every bridge means a `UIViewRepresentable`, layer lifetimes and state to keep
34
+ in sync.
35
+
36
+ ## The model value rule
37
+
38
+ Core Animation animates the presentation layer, a copy of the layer drawn on
39
+ screen. The model layer keeps whatever value you last assigned. When the
40
+ animation is removed, the presentation snaps back to the model. So every time
41
+ you add an animation, also assign the end value to the layer itself.
42
+
43
+ `fillMode = .forwards` with `isRemovedOnCompletion = false` hides the snap by
44
+ keeping the finished animation attached, but the model still holds the old
45
+ value, hit testing and later animations start from the wrong place, and the
46
+ animation object is never released. Set the model value instead; keep that pair
47
+ for the rare case where the model value must not change.
31
48
 
32
49
  ## CABasicAnimation
33
50
 
34
- [`CABasicAnimation`](https://sosumi.ai/documentation/quartzcore/cabasicanimation) interpolates a single layer property between two values.
35
-
36
- ### Basic Usage
51
+ One property, from one value to another:
37
52
 
38
53
  ```swift
39
- import QuartzCore
40
-
41
- let animation = CABasicAnimation(keyPath: "opacity")
42
- animation.fromValue = 0.0
43
- animation.toValue = 1.0
44
- animation.duration = 0.3
45
- animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
46
-
47
- // Apply to a layer
48
- layer.add(animation, forKey: "fadeIn")
49
- layer.opacity = 1.0 // Set the final model value
54
+ let fade = CABasicAnimation(keyPath: "opacity")
55
+ fade.fromValue = badgeLayer.opacity
56
+ fade.toValue = 0.2
57
+ fade.duration = 0.3
58
+ fade.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
59
+ badgeLayer.add(fade, forKey: "fade")
60
+ badgeLayer.opacity = 0.2 // model value
50
61
  ```
51
62
 
52
- ### Custom Bezier Timing
63
+ A custom Bezier curve on a position:
53
64
 
54
65
  ```swift
55
- // Custom cubic Bezier curve -- not available in SwiftUI
56
- let timingFunction = CAMediaTimingFunction(controlPoints: 0.2, 0.8, 0.2, 1.0)
57
-
58
- let animation = CABasicAnimation(keyPath: "position.y")
59
- animation.fromValue = layer.position.y
60
- animation.toValue = layer.position.y - 100
61
- animation.duration = 0.5
62
- animation.timingFunction = timingFunction
63
- animation.fillMode = .forwards
64
- animation.isRemovedOnCompletion = false
65
-
66
- layer.add(animation, forKey: "customBezier")
66
+ let rise = CABasicAnimation(keyPath: "position.y")
67
+ rise.fromValue = tagLayer.position.y
68
+ rise.toValue = tagLayer.position.y - 80
69
+ rise.duration = 0.5
70
+ rise.timingFunction = CAMediaTimingFunction(controlPoints: 0.16, 1.0, 0.3, 1.0)
71
+ tagLayer.add(rise, forKey: "rise")
72
+ tagLayer.position.y -= 80
67
73
  ```
68
74
 
69
- ### Shadow Path Animation
75
+ Animating `shadowPath`, which SwiftUI has no handle on:
70
76
 
71
77
  ```swift
72
- // Animate shadowPath -- not possible in pure SwiftUI
73
- let animation = CABasicAnimation(keyPath: "shadowPath")
74
- animation.fromValue = layer.shadowPath
75
- animation.toValue = UIBezierPath(roundedRect: newBounds, cornerRadius: 16).cgPath
76
- animation.duration = 0.3
77
- animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
78
-
79
- layer.shadowPath = UIBezierPath(roundedRect: newBounds, cornerRadius: 16).cgPath
80
- layer.add(animation, forKey: "shadowPath")
78
+ let target = UIBezierPath(roundedRect: tileLayer.bounds, cornerRadius: 16).cgPath
79
+ let reshape = CABasicAnimation(keyPath: "shadowPath")
80
+ reshape.fromValue = tileLayer.shadowPath
81
+ reshape.toValue = target
82
+ reshape.duration = 0.35
83
+ tileLayer.add(reshape, forKey: "shadowPath")
84
+ tileLayer.shadowPath = target
81
85
  ```
82
86
 
83
- **Important:** Always set the model value (the property on the layer itself) to the final state. Core Animation operates on a separate presentation layer -- without setting the model value, the layer snaps back when the animation completes.
84
-
85
- > **Docs:** [CABasicAnimation](https://sosumi.ai/documentation/quartzcore/cabasicanimation) | [CAMediaTimingFunction](https://sosumi.ai/documentation/quartzcore/camediatimingfunction)
86
-
87
87
  ## CAKeyframeAnimation
88
88
 
89
- [`CAKeyframeAnimation`](https://sosumi.ai/documentation/quartzcore/cakeyframeanimation) animates a property through a sequence of values or along a path.
90
-
91
- ### Value-Based Keyframes
89
+ ### Through a list of values
92
90
 
93
91
  ```swift
94
- let animation = CAKeyframeAnimation(keyPath: "transform.scale")
95
- animation.values = [1.0, 1.3, 0.9, 1.05, 1.0]
96
- animation.keyTimes = [0, 0.25, 0.5, 0.75, 1.0] // Normalized [0..1]
97
- animation.duration = 0.6
98
- animation.timingFunctions = [
99
- CAMediaTimingFunction(name: .easeOut),
100
- CAMediaTimingFunction(name: .easeIn),
92
+ let pop = CAKeyframeAnimation(keyPath: "transform.scale")
93
+ pop.values = [1.0, 1.25, 0.92, 1.04, 1.0]
94
+ pop.keyTimes = [0, 0.3, 0.55, 0.8, 1.0] // fractions of duration
95
+ pop.duration = 0.6
96
+ pop.timingFunctions = [
101
97
  CAMediaTimingFunction(name: .easeOut),
102
- CAMediaTimingFunction(name: .easeInEaseOut)
98
+ CAMediaTimingFunction(name: .easeInEaseOut),
99
+ CAMediaTimingFunction(name: .easeInEaseOut),
100
+ CAMediaTimingFunction(name: .easeIn)
103
101
  ]
104
-
105
- layer.add(animation, forKey: "bounceScale")
102
+ iconLayer.add(pop, forKey: "pop")
106
103
  ```
107
104
 
108
- ### Path-Based Animation
105
+ ### Along a path
109
106
 
110
107
  ```swift
111
- // Animate position along a CGPath -- unique to CAKeyframeAnimation
112
- let path = CGMutablePath()
113
- path.move(to: CGPoint(x: 50, y: 300))
114
- path.addCurve(
115
- to: CGPoint(x: 300, y: 50),
116
- control1: CGPoint(x: 100, y: 50),
117
- control2: CGPoint(x: 250, y: 300)
118
- )
119
-
120
- let animation = CAKeyframeAnimation(keyPath: "position")
121
- animation.path = path
122
- animation.duration = 1.5
123
- animation.rotationMode = .rotateAuto // Rotate along the tangent
124
- animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
125
-
126
- layer.add(animation, forKey: "pathAnimation")
127
- layer.position = CGPoint(x: 300, y: 50)
108
+ let route = CGMutablePath()
109
+ route.move(to: start)
110
+ route.addCurve(to: end, control1: CGPoint(x: start.x, y: end.y - 120),
111
+ control2: CGPoint(x: end.x, y: start.y - 120))
112
+
113
+ let fly = CAKeyframeAnimation(keyPath: "position")
114
+ fly.path = route
115
+ fly.rotationMode = .rotateAuto // face along the tangent
116
+ fly.duration = 0.9
117
+ planeLayer.add(fly, forKey: "fly")
118
+ planeLayer.position = end
128
119
  ```
129
120
 
130
- ### Shake Animation (Discrete Keyframes)
121
+ ### A shake helper
131
122
 
132
123
  ```swift
133
- func shakeAnimation() -> CAKeyframeAnimation {
134
- let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
135
- animation.values = [0, -10, 10, -8, 8, -5, 5, 0]
136
- animation.keyTimes = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 1.0]
137
- animation.duration = 0.5
138
- animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
139
- return animation
124
+ extension CALayer {
125
+ func shakeHorizontally() {
126
+ let shake = CAKeyframeAnimation(keyPath: "transform.translation.x")
127
+ shake.values = [0, -12, 12, -9, 9, -4, 4, 0]
128
+ shake.keyTimes = [0, 0.14, 0.28, 0.42, 0.57, 0.71, 0.85, 1]
129
+ shake.duration = 0.5
130
+ shake.timingFunction = CAMediaTimingFunction(name: .easeOut)
131
+ add(shake, forKey: "shake")
132
+ }
140
133
  }
141
134
  ```
142
135
 
143
- > **Docs:** [CAKeyframeAnimation](https://sosumi.ai/documentation/quartzcore/cakeyframeanimation)
136
+ The values return to zero, so no model value needs setting.
144
137
 
145
138
  ## CASpringAnimation
146
139
 
147
- [`CASpringAnimation`](https://sosumi.ai/documentation/quartzcore/caspringanimation) applies spring physics to a layer property. It extends `CABasicAnimation` with physical spring attributes.
148
-
149
- ### Physical Spring Parameters
140
+ A `CABasicAnimation` subclass with `mass`, `stiffness`, `damping` and
141
+ `initialVelocity`:
150
142
 
151
143
  ```swift
152
- let spring = CASpringAnimation(keyPath: "transform.scale")
153
- spring.fromValue = 0.0
154
- spring.toValue = 1.0
155
- spring.mass = 1.0
156
- spring.stiffness = 200.0
157
- spring.damping = 10.0
158
- spring.initialVelocity = 0.0
159
- spring.duration = spring.settlingDuration // Use the physics-calculated duration
160
-
161
- layer.add(spring, forKey: "springScale")
162
- layer.transform = CATransform3DIdentity
144
+ let settle = CASpringAnimation(keyPath: "transform.scale")
145
+ settle.mass = 1
146
+ settle.stiffness = 180
147
+ settle.damping = 14
148
+ settle.initialVelocity = 0
149
+ settle.fromValue = 0.85
150
+ settle.toValue = 1.0
151
+ settle.duration = settle.settlingDuration // let the spring finish
152
+ cardLayer.add(settle, forKey: "settle")
153
+ cardLayer.transform = CATransform3DIdentity
163
154
  ```
164
155
 
165
- ### Perceptual Spring (iOS 17+)
156
+ From iOS 17 the perceptual initializer matches SwiftUI's
157
+ `Spring(duration:bounce:)`:
166
158
 
167
159
  ```swift
168
- let spring = CASpringAnimation(perceptualDuration: 0.5, bounce: 0.3)
169
- spring.keyPath = "position.y"
170
- spring.fromValue = layer.position.y
171
- spring.toValue = layer.position.y - 100
172
-
173
- layer.add(spring, forKey: "perceptualSpring")
174
- layer.position.y -= 100
160
+ let settle = CASpringAnimation(perceptualDuration: 0.45, bounce: 0.2)
161
+ settle.keyPath = "transform.scale"
162
+ settle.fromValue = 0.85
163
+ settle.toValue = 1.0
175
164
  ```
176
165
 
177
- The `perceptualDuration` and `bounce` initializer matches SwiftUI's `Spring(duration:bounce:)`, making it easier to keep CA and SwiftUI spring behaviors consistent.
178
-
179
- ### Matching SwiftUI Spring Presets
166
+ Rough equivalents of the SwiftUI presets. All three presets default to a
167
+ 0.5 second duration and differ in bounce:
180
168
 
181
- | SwiftUI Preset | CA Equivalent |
182
- |---------------|---------------|
183
- | `.smooth` | `CASpringAnimation(perceptualDuration: 0.5, bounce: 0.0)` |
184
- | `.snappy` | `CASpringAnimation(perceptualDuration: 0.4, bounce: 0.15)` |
185
- | `.bouncy` | `CASpringAnimation(perceptualDuration: 0.5, bounce: 0.3)` |
186
-
187
- > **Docs:** [CASpringAnimation](https://sosumi.ai/documentation/quartzcore/caspringanimation)
169
+ | SwiftUI | `perceptualDuration` | `bounce` |
170
+ |---|---|---|
171
+ | `.smooth` | 0.5 | 0.0 |
172
+ | `.snappy` | 0.5 | 0.15 |
173
+ | `.bouncy` | 0.5 | 0.3 |
188
174
 
189
175
  ## CAAnimationGroup
190
176
 
191
- [`CAAnimationGroup`](https://sosumi.ai/documentation/quartzcore/caanimationgroup) runs multiple animations concurrently on the same layer.
177
+ Several animations on one layer, sharing timing:
192
178
 
193
179
  ```swift
194
- let scaleAnim = CABasicAnimation(keyPath: "transform.scale")
195
- scaleAnim.fromValue = 0.5
196
- scaleAnim.toValue = 1.0
197
-
198
- let opacityAnim = CABasicAnimation(keyPath: "opacity")
199
- opacityAnim.fromValue = 0.0
200
- opacityAnim.toValue = 1.0
201
-
202
- let group = CAAnimationGroup()
203
- group.animations = [scaleAnim, opacityAnim]
204
- group.duration = 0.4
205
- group.timingFunction = CAMediaTimingFunction(name: .easeOut)
206
-
207
- layer.add(group, forKey: "appearGroup")
208
- layer.transform = CATransform3DIdentity
209
- layer.opacity = 1.0
180
+ let grow = CABasicAnimation(keyPath: "transform.scale")
181
+ grow.fromValue = 0.6
182
+ grow.toValue = 1.0
183
+
184
+ let show = CABasicAnimation(keyPath: "opacity")
185
+ show.fromValue = 0
186
+ show.toValue = 1
187
+
188
+ let entrance = CAAnimationGroup()
189
+ entrance.animations = [grow, show]
190
+ entrance.duration = 0.4
191
+ entrance.timingFunction = CAMediaTimingFunction(name: .easeOut)
192
+ bannerLayer.add(entrance, forKey: "entrance")
193
+ bannerLayer.transform = CATransform3DIdentity
194
+ bannerLayer.opacity = 1
210
195
  ```
211
196
 
212
- > **Docs:** [CAAnimationGroup](https://sosumi.ai/documentation/quartzcore/caanimationgroup)
213
-
214
197
  ## CADisplayLink
215
198
 
216
- [`CADisplayLink`](https://sosumi.ai/documentation/quartzcore/cadisplaylink) is a timer synchronized to the display's refresh rate. Use it for frame-accurate custom drawing, particle systems, or manual animation loops.
217
-
218
- ### Basic Display Link
199
+ A timer that fires in step with the display.
219
200
 
220
201
  ```swift
221
- import QuartzCore
222
-
223
- final class FrameAnimator {
224
- private var displayLink: CADisplayLink?
202
+ @MainActor
203
+ final class CountUpDriver: NSObject {
204
+ private var link: CADisplayLink?
225
205
  private var startTime: CFTimeInterval = 0
206
+ private let length: CFTimeInterval = 2
207
+ var onFrame: (Double) -> Void = { _ in }
226
208
 
227
209
  func start() {
228
- displayLink = CADisplayLink(target: self, selector: #selector(onFrame))
229
- displayLink?.add(to: .main, forMode: .common)
210
+ guard link == nil else { return }
230
211
  startTime = CACurrentMediaTime()
212
+ let link = CADisplayLink(target: self, selector: #selector(frame(_:)))
213
+ link.preferredFrameRateRange = CAFrameRateRange(minimum: 60, maximum: 120, preferred: 120)
214
+ link.add(to: .main, forMode: .common)
215
+ self.link = link
231
216
  }
232
217
 
233
218
  func stop() {
234
- displayLink?.invalidate()
235
- displayLink = nil
219
+ link?.invalidate()
220
+ link = nil
236
221
  }
237
222
 
238
- @objc private func onFrame(_ link: CADisplayLink) {
239
- let elapsed = link.timestamp - startTime
240
- let progress = min(elapsed / 2.0, 1.0) // 2-second animation
241
-
242
- // Update rendering based on progress
243
- updateAnimation(progress: progress)
244
-
245
- if progress >= 1.0 {
246
- stop()
247
- }
248
- }
249
-
250
- private func updateAnimation(progress: Double) {
251
- // Custom per-frame rendering logic
223
+ @objc private func frame(_ link: CADisplayLink) {
224
+ let progress = min((link.timestamp - startTime) / length, 1)
225
+ onFrame(progress)
226
+ if progress >= 1 { stop() }
252
227
  }
253
228
  }
254
229
  ```
255
230
 
256
- ### ProMotion Frame Rate Control
231
+ Frame-rate guidance for `preferredFrameRateRange` on ProMotion displays:
257
232
 
258
- On ProMotion displays (120 Hz), use `preferredFrameRateRange` to balance smoothness and power:
233
+ | Preferred | For |
234
+ |---|---|
235
+ | 120 | scrolling and following a finger |
236
+ | 60 | ordinary animation |
237
+ | 30 | slow ambient motion, or saving power |
259
238
 
260
- ```swift
261
- displayLink?.preferredFrameRateRange = CAFrameRateRange(
262
- minimum: 30,
263
- maximum: 120,
264
- preferred: 60
265
- )
266
- ```
239
+ Always call `invalidate()` when finished. A display link left running keeps the
240
+ CPU awake and drains the battery, and it retains its target until invalidated.
267
241
 
268
- | Range | Use Case |
269
- |-------|----------|
270
- | `preferred: 120` | Smooth scrolling, gesture tracking |
271
- | `preferred: 60` | Standard animations |
272
- | `preferred: 30` | Ambient/slow animations, power saving |
273
-
274
- **Important:** Always call `invalidate()` when done. A running `CADisplayLink` prevents the CPU from idling and drains battery.
275
-
276
- > **Docs:** [CADisplayLink](https://sosumi.ai/documentation/quartzcore/cadisplaylink) | [Optimizing ProMotion refresh rates](https://sosumi.ai/documentation/quartzcore/optimizing-promotion-refresh-rates-for-iphone-13-pro-and-ipad-pro)
277
-
278
- ## UIViewRepresentable Wrapper for CA Layers
279
-
280
- To use Core Animation layers inside SwiftUI, wrap them in a `UIViewRepresentable`.
281
-
282
- ### Animated Layer View
242
+ ## Hosting layer animation in SwiftUI
283
243
 
284
244
  ```swift
285
- import SwiftUI
286
- import QuartzCore
287
-
288
- struct AnimatedLayerView: UIViewRepresentable {
245
+ struct SoundWave: UIViewRepresentable {
289
246
  var isAnimating: Bool
290
- var color: Color
247
+ var tint: UIColor
291
248
 
292
- func makeUIView(context: Context) -> AnimatedLayerUIView {
293
- let view = AnimatedLayerUIView()
294
- return view
249
+ func makeUIView(context: Context) -> SoundWaveView {
250
+ SoundWaveView()
295
251
  }
296
252
 
297
- func updateUIView(_ uiView: AnimatedLayerUIView, context: Context) {
298
- uiView.updateColor(UIColor(color))
299
-
253
+ func updateUIView(_ view: SoundWaveView, context: Context) {
254
+ view.setTint(tint)
300
255
  if isAnimating {
301
- uiView.startAnimation()
256
+ view.startWaving()
302
257
  } else {
303
- uiView.stopAnimation()
258
+ view.stopWaving()
304
259
  }
305
260
  }
306
261
 
307
- static func dismantleUIView(_ uiView: AnimatedLayerUIView, coordinator: ()) {
308
- uiView.stopAnimation()
262
+ static func dismantleUIView(_ view: SoundWaveView, coordinator: ()) {
263
+ view.stopWaving()
309
264
  }
310
265
  }
311
- ```
312
266
 
313
- ### The Backing UIView
314
-
315
- ```swift
316
- final class AnimatedLayerUIView: UIView {
317
- private let animationLayer = CAShapeLayer()
318
- private var displayLink: CADisplayLink?
267
+ final class SoundWaveView: UIView {
268
+ private let waveLayer = CAShapeLayer()
269
+ private var link: CADisplayLink?
319
270
  private var phase: CGFloat = 0
320
271
 
321
272
  override init(frame: CGRect) {
322
273
  super.init(frame: frame)
323
- setupLayer()
274
+ configureLayer()
324
275
  }
325
276
 
326
277
  required init?(coder: NSCoder) {
327
278
  super.init(coder: coder)
328
- setupLayer()
279
+ configureLayer()
329
280
  }
330
281
 
331
- private func setupLayer() {
332
- animationLayer.fillColor = UIColor.systemBlue.cgColor
333
- animationLayer.strokeColor = nil
334
- layer.addSublayer(animationLayer)
282
+ private func configureLayer() {
283
+ waveLayer.fillColor = UIColor.systemBlue.cgColor
284
+ layer.addSublayer(waveLayer)
335
285
  }
336
286
 
337
287
  override func layoutSubviews() {
338
288
  super.layoutSubviews()
339
- animationLayer.frame = bounds
340
- updatePath()
341
- }
342
-
343
- func updateColor(_ color: UIColor) {
344
- // Animate color change at the CA layer level
345
- let animation = CABasicAnimation(keyPath: "fillColor")
346
- animation.fromValue = animationLayer.fillColor
347
- animation.toValue = color.cgColor
348
- animation.duration = 0.3
349
-
350
- animationLayer.fillColor = color.cgColor
351
- animationLayer.add(animation, forKey: "colorChange")
289
+ waveLayer.frame = bounds
352
290
  }
353
291
 
354
- func startAnimation() {
355
- guard displayLink == nil else { return }
356
- displayLink = CADisplayLink(target: self, selector: #selector(tick))
357
- displayLink?.preferredFrameRateRange = CAFrameRateRange(
358
- minimum: 30, maximum: 60, preferred: 60
359
- )
360
- displayLink?.add(to: .main, forMode: .common)
292
+ func setTint(_ color: UIColor) {
293
+ guard waveLayer.fillColor != color.cgColor else { return }
294
+ let recolor = CABasicAnimation(keyPath: "fillColor")
295
+ recolor.fromValue = waveLayer.fillColor
296
+ recolor.toValue = color.cgColor
297
+ recolor.duration = 0.25
298
+ waveLayer.add(recolor, forKey: "fillColor")
299
+ waveLayer.fillColor = color.cgColor
361
300
  }
362
301
 
363
- func stopAnimation() {
364
- displayLink?.invalidate()
365
- displayLink = nil
302
+ func startWaving() {
303
+ guard link == nil else { return }
304
+ let link = CADisplayLink(target: self, selector: #selector(advanceWave(_:)))
305
+ link.preferredFrameRateRange = CAFrameRateRange(minimum: 20, maximum: 60, preferred: 30)
306
+ link.add(to: .main, forMode: .common)
307
+ self.link = link
366
308
  }
367
309
 
368
- @objc private func tick(_ link: CADisplayLink) {
369
- phase += 0.05
370
- updatePath()
310
+ func stopWaving() {
311
+ link?.invalidate()
312
+ link = nil
371
313
  }
372
314
 
373
- private func updatePath() {
374
- let path = CGMutablePath()
375
- let width = bounds.width
376
- let height = bounds.height
377
- let midY = height / 2
378
-
379
- path.move(to: CGPoint(x: 0, y: midY))
380
- for x in stride(from: 0, to: width, by: 2) {
381
- let relativeX = x / width
382
- let y = midY + sin((relativeX * .pi * 4) + phase) * (height * 0.3)
383
- path.addLine(to: CGPoint(x: x, y: y))
315
+ @objc private func advanceWave(_ link: CADisplayLink) {
316
+ phase += 0.12
317
+ let crest = stride(from: bounds.minX, through: bounds.maxX, by: 3).map { x in
318
+ CGPoint(x: x, y: bounds.midY + sin(x / 24 + phase) * 18)
384
319
  }
385
- path.addLine(to: CGPoint(x: width, y: height))
386
- path.addLine(to: CGPoint(x: 0, y: height))
387
- path.closeSubpath()
388
-
389
- animationLayer.path = path
320
+ let outline = CGMutablePath()
321
+ outline.addLines(between: [CGPoint(x: bounds.minX, y: bounds.maxY)]
322
+ + crest + [CGPoint(x: bounds.maxX, y: bounds.maxY)])
323
+ outline.closeSubpath()
324
+ waveLayer.path = outline
390
325
  }
391
326
  }
392
327
  ```
393
328
 
394
- ### SwiftUI Usage
329
+ In SwiftUI:
395
330
 
396
331
  ```swift
397
- struct WaveView: View {
398
- @State private var isAnimating = true
399
-
400
- var body: some View {
401
- Button { isAnimating.toggle() } label: {
402
- AnimatedLayerView(isAnimating: isAnimating, color: .blue)
403
- .frame(height: 200)
404
- .clipShape(.rect(cornerRadius: 16))
405
- }
406
- .buttonStyle(.plain)
407
- }
332
+ @State private var recording = false
333
+
334
+ Button { recording.toggle() } label: {
335
+ SoundWave(isAnimating: recording, tint: recording ? .systemRed : .systemBlue)
336
+ .frame(height: 200)
337
+ .clipShape(.rect(cornerRadius: 16))
408
338
  }
409
339
  ```
410
340
 
411
- ### Key Rules for CA-in-SwiftUI Wrappers
341
+ Rules for the wrapper:
412
342
 
413
- 1. **Create layers in `makeUIView` or the UIView subclass initializer**, not in `updateUIView`.
414
- 2. **Stop display links in `dismantleUIView`** to prevent leaks and background CPU usage.
415
- 3. **Guard against redundant animation starts** in `updateUIView` -- it runs on every SwiftUI state change.
416
- 4. **Set model values alongside CA animations** so the layer state is correct after animations complete.
343
+ 1. Layers are built once, when the view is made (`makeUIView` or the
344
+ `UIView` initializer). `updateUIView` only changes them.
345
+ 2. `dismantleUIView` is where display links get invalidated; skip it and they
346
+ leak and keep the CPU busy after the view is gone.
347
+ 3. SwiftUI calls `updateUIView` for any relevant state change, so a start call
348
+ there must be idempotent.
349
+ 4. Assign model values next to every Core Animation animation.
417
350
 
418
- ## Bridging CA Animations with SwiftUI State
351
+ ## Moving state across the bridge
419
352
 
420
- ### Triggering CA Animations from SwiftUI State Changes
353
+ ### Firing an animation from SwiftUI
421
354
 
422
- ```swift
423
- struct PulseButton: UIViewRepresentable {
424
- var pulseCount: Int // Increment to trigger a pulse
355
+ Pass a counter in. The coordinator remembers the last value, so the view
356
+ animates only when the counter actually changes:
425
357
 
426
- func makeUIView(context: Context) -> PulseUIView {
427
- PulseUIView()
428
- }
358
+ ```swift
359
+ struct Ripple: UIViewRepresentable {
360
+ var fireCount: Int
429
361
 
430
- func updateUIView(_ uiView: PulseUIView, context: Context) {
431
- // Only animate when pulseCount changes, not on every update
432
- if context.coordinator.lastPulseCount != pulseCount {
433
- context.coordinator.lastPulseCount = pulseCount
434
- uiView.pulse()
435
- }
362
+ final class Coordinator {
363
+ var lastFired = 0
436
364
  }
437
365
 
438
366
  func makeCoordinator() -> Coordinator { Coordinator() }
439
367
 
440
- final class Coordinator {
441
- var lastPulseCount = 0
368
+ func makeUIView(context: Context) -> RippleView { RippleView() }
369
+
370
+ func updateUIView(_ view: RippleView, context: Context) {
371
+ guard fireCount != context.coordinator.lastFired else { return }
372
+ context.coordinator.lastFired = fireCount
373
+ view.emit()
442
374
  }
443
375
  }
444
376
 
445
- final class PulseUIView: UIView {
446
- private let pulseLayer = CAShapeLayer()
377
+ final class RippleView: UIView {
378
+ private let ring = CAShapeLayer()
447
379
 
448
380
  override init(frame: CGRect) {
449
381
  super.init(frame: frame)
450
- pulseLayer.fillColor = UIColor.systemBlue.withAlphaComponent(0.3).cgColor
451
- layer.addSublayer(pulseLayer)
382
+ ring.fillColor = UIColor.systemTeal.cgColor
383
+ ring.opacity = 0
384
+ layer.addSublayer(ring)
452
385
  }
453
386
 
454
- required init?(coder: NSCoder) { fatalError() }
387
+ required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
455
388
 
456
389
  override func layoutSubviews() {
457
390
  super.layoutSubviews()
458
- let size = min(bounds.width, bounds.height)
459
- let rect = CGRect(
460
- x: (bounds.width - size) / 2,
461
- y: (bounds.height - size) / 2,
462
- width: size,
463
- height: size
464
- )
465
- pulseLayer.path = UIBezierPath(ovalIn: rect).cgPath
391
+ ring.frame = bounds
392
+ ring.path = UIBezierPath(ovalIn: bounds.insetBy(dx: bounds.width / 4, dy: bounds.height / 4)).cgPath
466
393
  }
467
394
 
468
- func pulse() {
469
- let scaleAnim = CABasicAnimation(keyPath: "transform.scale")
470
- scaleAnim.fromValue = 1.0
471
- scaleAnim.toValue = 1.5
472
-
473
- let opacityAnim = CABasicAnimation(keyPath: "opacity")
474
- opacityAnim.fromValue = 1.0
475
- opacityAnim.toValue = 0.0
476
-
477
- let group = CAAnimationGroup()
478
- group.animations = [scaleAnim, opacityAnim]
479
- group.duration = 0.6
480
- group.timingFunction = CAMediaTimingFunction(name: .easeOut)
481
-
482
- pulseLayer.add(group, forKey: "pulse")
395
+ func emit() {
396
+ let spread = CABasicAnimation(keyPath: "transform.scale")
397
+ spread.fromValue = 1.0
398
+ spread.toValue = 1.5
399
+ let fade = CABasicAnimation(keyPath: "opacity")
400
+ fade.fromValue = 1.0
401
+ fade.toValue = 0.0
402
+
403
+ let burst = CAAnimationGroup()
404
+ burst.animations = [spread, fade]
405
+ burst.duration = 0.6
406
+ burst.timingFunction = CAMediaTimingFunction(name: .easeOut)
407
+ ring.add(burst, forKey: "burst")
408
+ ring.opacity = 0
483
409
  }
484
410
  }
485
411
  ```
486
412
 
487
- ### Reading CA Animation Completion in SwiftUI
413
+ ### Reporting completion back to SwiftUI
488
414
 
489
- Use `CAAnimationDelegate` on the Coordinator to report animation completion back to SwiftUI:
415
+ The coordinator is the animation's delegate. It owns the link back to SwiftUI,
416
+ so `animationDidStop` can write through the `Binding`:
490
417
 
491
418
  ```swift
492
- struct AnimatedBadge: UIViewRepresentable {
493
- @Binding var isAnimationComplete: Bool
494
-
495
- func makeCoordinator() -> Coordinator { Coordinator(self) }
419
+ struct SpringyBadge: UIViewRepresentable {
420
+ var trigger: Int
421
+ @Binding var settled: Bool
496
422
 
497
- func makeUIView(context: Context) -> UIView {
498
- let view = UIView()
499
- let badge = CAShapeLayer()
500
- badge.path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 40, height: 40)).cgPath
501
- badge.fillColor = UIColor.systemRed.cgColor
502
- badge.name = "badge"
503
- view.layer.addSublayer(badge)
504
- return view
505
- }
506
-
507
- func updateUIView(_ uiView: UIView, context: Context) {}
508
-
509
- func animateIn(_ uiView: UIView) {
510
- guard let badge = uiView.layer.sublayers?.first(where: { $0.name == "badge" }) else { return }
423
+ @MainActor
424
+ final class Coordinator: NSObject, CAAnimationDelegate {
425
+ var parent: SpringyBadge
426
+ var lastTrigger = 0
511
427
 
512
- let spring = CASpringAnimation(perceptualDuration: 0.5, bounce: 0.3)
513
- spring.keyPath = "transform.scale"
514
- spring.fromValue = 0.0
515
- spring.toValue = 1.0
516
- spring.delegate = uiView.next as? CAAnimationDelegate
428
+ init(owner: SpringyBadge) { parent = owner }
517
429
 
518
- badge.add(spring, forKey: "appear")
519
- badge.transform = CATransform3DIdentity
430
+ nonisolated func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
431
+ guard flag else { return }
432
+ MainActor.assumeIsolated { parent.settled = true }
433
+ }
520
434
  }
521
435
 
522
- final class Coordinator: NSObject, CAAnimationDelegate {
523
- var parent: AnimatedBadge
436
+ func makeCoordinator() -> Coordinator { Coordinator(owner: self) }
524
437
 
525
- init(_ parent: AnimatedBadge) { self.parent = parent }
438
+ func makeUIView(context: Context) -> UIView {
439
+ let host = UIView()
440
+ let dot = CAShapeLayer()
441
+ dot.name = "dot"
442
+ dot.path = CGPath(ellipseIn: CGRect(origin: .zero, size: CGSize(width: 40, height: 40)), transform: nil)
443
+ dot.fillColor = UIColor.systemOrange.cgColor
444
+ host.layer.addSublayer(dot)
445
+ return host
446
+ }
526
447
 
527
- func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
528
- if flag {
529
- parent.isAnimationComplete = true
530
- }
531
- }
448
+ func updateUIView(_ host: UIView, context: Context) {
449
+ context.coordinator.parent = self // keep the binding current
450
+ guard trigger != context.coordinator.lastTrigger,
451
+ let dot = host.layer.sublayers?.first(where: { $0.name == "dot" })
452
+ else { return }
453
+ context.coordinator.lastTrigger = trigger
454
+
455
+ let grow = CASpringAnimation(perceptualDuration: 0.4, bounce: 0.35)
456
+ grow.keyPath = "transform.scale"
457
+ grow.fromValue = 0.6
458
+ grow.toValue = 1.0
459
+ grow.delegate = context.coordinator
460
+ dot.add(grow, forKey: "grow")
461
+ dot.transform = CATransform3DIdentity
532
462
  }
533
463
  }
534
464
  ```
535
465
 
536
- ## Performance Considerations
537
-
538
- ### CA vs. SwiftUI Animation Performance
539
-
540
- | Aspect | SwiftUI Animation | Core Animation |
541
- |--------|------------------|----------------|
542
- | **Rendering** | View diffing + render tree | Direct layer manipulation |
543
- | **Thread** | Main thread for state, render server for compositing | Same -- render server composites |
544
- | **Overhead** | SwiftUI body re-evaluation per frame (for animatable) | No body re-evaluation |
545
- | **Best for** | Standard UI transitions | Particle effects, wave animations, complex paths |
546
-
547
- ### Guidelines
548
-
549
- - **Avoid mixing CA animations and SwiftUI animations on the same property.** They use separate animation systems and will conflict.
550
- - **Use `CADisplayLink` sparingly.** A running display link prevents the CPU from sleeping. Always invalidate when not needed.
551
- - **Prefer `CAShapeLayer` for path-based animations** over redrawing in `draw(_:)`. Shape layers are GPU-accelerated.
552
- - **Set `shouldRasterize = true`** on complex static sublayer trees to cache them as bitmaps, but disable it during animation (rasterization prevents smooth per-frame updates).
553
- - **Match CA spring parameters to SwiftUI springs** using the `perceptualDuration:bounce:` initializer so animations feel consistent across the bridge boundary.
466
+ The delegate must be `context.coordinator`. A plain `UIView` does not conform
467
+ to `CAAnimationDelegate`, so looking one up through the responder chain gives
468
+ `nil` and the callback never fires. Refreshing `coordinator.parent` in
469
+ `updateUIView` keeps the binding pointing at the current state.
470
+
471
+ Reset `settled` to `false` in the SwiftUI code that increments `trigger`, not
472
+ inside `updateUIView`; writing state during a view update triggers a runtime
473
+ warning and an extra render pass.
474
+
475
+ ## Performance
476
+
477
+ | | SwiftUI animation | Core Animation |
478
+ |---|---|---|
479
+ | Mechanism | view diffing and the render tree | direct layer changes |
480
+ | Per frame | `body` of animatable views re-evaluated as data interpolates | no `body` evaluation |
481
+ | Best for | ordinary UI transitions | particles, waves, complex paths |
482
+
483
+ Both end up composited by the render server, and both keep their state on the
484
+ main thread.
485
+
486
+ - Never animate one property from both systems. They do not coordinate and
487
+ will fight.
488
+ - Use `CADisplayLink` sparingly and invalidate it the moment it is not needed.
489
+ - For animated paths, update a `CAShapeLayer` rather than redrawing in
490
+ `draw(_:)`; shape layers are rendered on the GPU.
491
+ - `shouldRasterize = true` caches a complex, static sublayer tree as a bitmap.
492
+ Turn it off while that tree animates, or the cache is rebuilt every frame.
493
+ - Build Core Animation springs with `perceptualDuration:bounce:` so they feel
494
+ like the SwiftUI springs next to them.
495
+
496
+ ## Apple documentation
497
+
498
+ - [CABasicAnimation](https://developer.apple.com/documentation/quartzcore/cabasicanimation)
499
+ - [CAMediaTimingFunction](https://developer.apple.com/documentation/quartzcore/camediatimingfunction)
500
+ - [CAKeyframeAnimation](https://developer.apple.com/documentation/quartzcore/cakeyframeanimation)
501
+ - [CASpringAnimation](https://developer.apple.com/documentation/quartzcore/caspringanimation)
502
+ - [CAAnimationGroup](https://developer.apple.com/documentation/quartzcore/caanimationgroup)
503
+ - [CADisplayLink](https://developer.apple.com/documentation/quartzcore/cadisplaylink)
504
+ - [Optimizing ProMotion refresh rates](https://developer.apple.com/documentation/quartzcore/optimizing-promotion-refresh-rates-for-iphone-13-pro-and-ipad-pro)