@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,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)