@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,736 +1,614 @@
1
- # Vision Request Patterns
1
+ # Vision request recipes
2
2
 
3
- Complete implementation patterns for Vision framework requests covering text
4
- recognition, face detection, barcode scanning, segmentation, classification,
5
- and video processing. All patterns target iOS 26+ with Swift 6.3 unless noted.
3
+ Longer examples for the requests summarized in `../SKILL.md`. Swift-native
4
+ examples need iOS 18 or later unless marked otherwise; legacy examples are
5
+ labelled.
6
6
 
7
7
  ## Contents
8
- - Complete Text Recognition Pipeline
9
- - Face Detection with Landmarks
10
- - Barcode Detection with All Symbologies
11
- - Person Segmentation with Mask Application
12
- - Instance Segmentation (iOS 18+)
13
- - Image Classification
14
- - Saliency Detection
15
- - Rectangle Detection
16
- - Horizon Detection
17
- - Batch Processing Multiple Requests
18
- - Video Frame Processing with CMSampleBuffer
19
- - Object Tracking Across Video Frames
20
- - Coordinate Normalization Utilities
21
- - Performance Considerations
22
-
23
- ## Complete Text Recognition Pipeline
24
-
25
- Full pipeline from image loading through text extraction with coordinate mapping.
26
8
 
27
- ```swift
28
- import Vision
29
- import UIKit
9
+ - [Text recognition pipeline](#text-recognition-pipeline)
10
+ - [Faces with landmarks and quality](#faces-with-landmarks-and-quality)
11
+ - [Barcodes](#barcodes)
12
+ - [Person segmentation and background blur](#person-segmentation-and-background-blur)
13
+ - [Classification](#classification)
14
+ - [Saliency](#saliency)
15
+ - [Rectangles](#rectangles)
16
+ - [Horizon](#horizon)
17
+ - [Several requests on one image](#several-requests-on-one-image)
18
+ - [Camera frames from AVFoundation](#camera-frames-from-avfoundation)
19
+ - [Tracking across frames](#tracking-across-frames)
20
+ - [Coordinate helpers](#coordinate-helpers)
21
+ - [Performance](#performance)
30
22
 
31
- @MainActor
32
- final class TextRecognizer {
33
- func recognizeText(in image: UIImage) async throws -> [RecognizedTextBlock] {
34
- guard let cgImage = image.cgImage else {
35
- throw TextRecognitionError.invalidImage
36
- }
23
+ ## Text recognition pipeline
37
24
 
38
- var request = RecognizeTextRequest()
39
- request.recognitionLevel = .accurate
40
- request.recognitionLanguages = [
41
- Locale.Language(identifier: "en-US"),
42
- ]
43
- request.usesLanguageCorrection = true
25
+ A reusable reader that turns a `UIImage` into lines with pixel-space frames.
44
26
 
45
- let observations = try await request.perform(on: cgImage)
46
- let imageSize = CGSize(
47
- width: cgImage.width,
48
- height: cgImage.height
49
- )
27
+ ```swift
28
+ import UIKit
29
+ import Vision
50
30
 
51
- return observations.compactMap { observation in
52
- guard let candidate = observation.topCandidates(1).first else { return nil }
53
- let boundingBox = observation.boundingBox
54
- let imageRect = VNImageRectForNormalizedRect(
55
- boundingBox,
56
- Int(imageSize.width),
57
- Int(imageSize.height)
58
- )
59
- return RecognizedTextBlock(
60
- text: candidate.string,
61
- confidence: candidate.confidence,
62
- boundingBox: imageRect
63
- )
64
- }
31
+ extension RecognizedTextObservation {
32
+ var bestString: String? {
33
+ topCandidates(1).first?.string
65
34
  }
66
35
  }
67
36
 
68
- struct RecognizedTextBlock: Sendable {
37
+ struct ScannedLine: Sendable {
69
38
  let text: String
70
39
  let confidence: Float
71
- let boundingBox: CGRect
40
+ let frame: CGRect
72
41
  }
73
42
 
74
- enum TextRecognitionError: Error {
75
- case invalidImage
43
+ enum ReceiptReaderError: Error {
44
+ case noBitmap
45
+ }
46
+
47
+ struct ReceiptReader {
48
+ func lines(in picture: UIImage) async throws -> [ScannedLine] {
49
+ guard let bitmap = picture.cgImage else { throw ReceiptReaderError.noBitmap }
50
+ var request = RecognizeTextRequest()
51
+ request.recognitionLevel = .accurate
52
+ request.recognitionLanguages = [Locale.Language(identifier: "en-CA")]
53
+ request.usesLanguageCorrection = true
54
+ let found = try await request.perform(on: bitmap)
55
+ return found.compactMap { observation in
56
+ guard let best = observation.topCandidates(1).first else {
57
+ return nil
58
+ }
59
+ let frame = VNImageRectForNormalizedRect(
60
+ observation.boundingBox.cgRect, bitmap.width, bitmap.height
61
+ )
62
+ return ScannedLine(text: best.string, confidence: best.confidence, frame: frame)
63
+ }
64
+ }
76
65
  }
77
66
  ```
78
67
 
79
- ### Text Recognition with Language Hints
68
+ The `bestString` extension is reused by later examples. `boundingBox` is a
69
+ `NormalizedRect`; pass `.cgRect` to the C helper, or call
70
+ `boundingBox.toImageCoordinates(size, origin: .upperLeft)` to get a top-left
71
+ rectangle in one step.
72
+
73
+ ### Several languages and domain words
80
74
 
81
75
  ```swift
82
- func recognizeMultilingualText(in cgImage: CGImage) async throws -> [String] {
76
+ import Vision
77
+
78
+ func multilingualRequest() -> RecognizeTextRequest {
83
79
  var request = RecognizeTextRequest()
84
- request.recognitionLevel = .accurate
85
80
  request.recognitionLanguages = [
81
+ Locale.Language(identifier: "nl-NL"),
86
82
  Locale.Language(identifier: "en-US"),
87
- Locale.Language(identifier: "fr-FR"),
88
- Locale.Language(identifier: "de-DE"),
83
+ Locale.Language(identifier: "es-ES")
89
84
  ]
90
- request.usesLanguageCorrection = true
91
- request.customWords = ["iOS", "SwiftUI", "Xcode"]
92
-
93
- let observations = try await request.perform(on: cgImage)
94
- return observations.compactMap { $0.topCandidates(1).first?.string }
85
+ request.customWords = ["visionOS", "TestFlight", "Xcode"]
86
+ return request
95
87
  }
96
88
  ```
97
89
 
98
- ### Fast Text Recognition for Live Video
90
+ List languages in priority order. `customWords` keeps the corrector from
91
+ "fixing" product names and jargon.
92
+
93
+ ### Fast recognition on camera frames
99
94
 
100
95
  ```swift
101
- func recognizeTextFast(in sampleBuffer: CMSampleBuffer) async throws -> [String] {
96
+ import CoreMedia
97
+ import Vision
98
+
99
+ func quickLines(in frame: CMSampleBuffer) async throws -> [String] {
102
100
  var request = RecognizeTextRequest()
103
101
  request.recognitionLevel = .fast
104
- request.recognitionLanguages = [Locale.Language(identifier: "en-US")]
105
-
106
- let observations = try await request.perform(on: sampleBuffer)
107
- return observations.compactMap { $0.topCandidates(1).first?.string }
102
+ let found = try await request.perform(on: frame)
103
+ return found.compactMap(\.bestString)
108
104
  }
109
105
  ```
110
106
 
111
- ### Legacy Text Recognition (Pre-iOS 18)
107
+ A `CMSampleBuffer` goes straight into `perform(on:)`; no conversion needed.
108
+
109
+ ### Legacy version with a completion wrapper
112
110
 
113
111
  ```swift
114
112
  import Vision
115
113
 
116
- func recognizeTextLegacy(
117
- in cgImage: CGImage,
118
- completion: @escaping ([String]) -> Void
119
- ) {
120
- let request = VNRecognizeTextRequest { request, error in
121
- guard error == nil,
122
- let observations = request.results as? [VNRecognizedTextObservation]
123
- else {
124
- completion([])
125
- return
126
- }
127
- let strings = observations.compactMap {
128
- $0.topCandidates(1).first?.string
114
+ func legacyLines(in bitmap: CGImage) async throws -> [String] {
115
+ try await withCheckedThrowingContinuation { continuation in
116
+ DispatchQueue.global(qos: .userInitiated).async {
117
+ let request = VNRecognizeTextRequest { finished, failure in
118
+ if let failure {
119
+ continuation.resume(throwing: failure)
120
+ return
121
+ }
122
+ let found = finished.results as? [VNRecognizedTextObservation] ?? []
123
+ continuation.resume(returning: found.compactMap { $0.topCandidates(1).first }.map(\.string))
124
+ }
125
+ request.recognitionLevel = .accurate
126
+ request.recognitionLanguages = ["en-US", "pt-BR"]
127
+ do {
128
+ try VNImageRequestHandler(cgImage: bitmap).perform([request])
129
+ } catch {
130
+ continuation.resume(throwing: error)
131
+ }
129
132
  }
130
- completion(strings)
131
- }
132
- request.recognitionLevel = .accurate
133
- request.recognitionLanguages = ["en-US"]
134
- request.usesLanguageCorrection = true
135
-
136
- let handler = VNImageRequestHandler(cgImage: cgImage)
137
- DispatchQueue.global(qos: .userInitiated).async {
138
- try? handler.perform([request])
139
133
  }
140
134
  }
141
135
  ```
142
136
 
143
- ## Face Detection with Landmarks
137
+ `VNRecognizeTextRequest` needs iOS 13. When `perform` throws, the completion
138
+ does not run, so the continuation is resumed exactly once either way.
139
+
140
+ ## Faces with landmarks and quality
141
+
142
+ Run the three requests and join the results by position. Guard every index,
143
+ because the arrays are not guaranteed to line up in length.
144
144
 
145
145
  ```swift
146
146
  import Vision
147
147
 
148
- struct DetectedFace: Sendable {
149
- let boundingBox: CGRect
150
- let landmarks: FaceLandmarkPoints?
151
- let roll: Measurement<UnitAngle>
152
- let yaw: Measurement<UnitAngle>
153
- let captureQuality: FaceObservation.CaptureQuality?
154
- }
155
-
156
- struct FaceLandmarkPoints: Sendable {
157
- let leftEye: [CGPoint]
158
- let rightEye: [CGPoint]
159
- let nose: [CGPoint]
160
- let outerLips: [CGPoint]
161
- let faceContour: [CGPoint]
162
- }
163
-
164
- func detectFaces(in cgImage: CGImage) async throws -> [DetectedFace] {
165
- // Detect face rectangles
166
- let rectRequest = DetectFaceRectanglesRequest()
167
- let faces = try await rectRequest.perform(on: cgImage)
168
-
169
- // Detect landmarks for detailed features
170
- let landmarkRequest = DetectFaceLandmarksRequest()
171
- let landmarkFaces = try await landmarkRequest.perform(on: cgImage)
172
-
173
- // Detect capture quality for photo selection
174
- let qualityRequest = DetectFaceCaptureQualityRequest()
175
- let qualityFaces = try await qualityRequest.perform(on: cgImage)
176
-
177
- return faces.enumerated().map { index, face in
178
- let landmarks: FaceLandmarkPoints?
179
- if index < landmarkFaces.count,
180
- let lm = landmarkFaces[index].landmarks {
181
- landmarks = FaceLandmarkPoints(
182
- leftEye: lm.leftEye?.normalizedPoints ?? [],
183
- rightEye: lm.rightEye?.normalizedPoints ?? [],
184
- nose: lm.nose?.normalizedPoints ?? [],
185
- outerLips: lm.outerLips?.normalizedPoints ?? [],
186
- faceContour: lm.faceContour?.normalizedPoints ?? []
187
- )
188
- } else {
189
- landmarks = nil
190
- }
191
-
192
- let quality: FaceObservation.CaptureQuality?
193
- if index < qualityFaces.count {
194
- quality = qualityFaces[index].captureQuality
195
- } else {
196
- quality = nil
197
- }
148
+ struct FaceReport: Sendable {
149
+ let box: NormalizedRect
150
+ let eyes: [NormalizedPoint]
151
+ let nose: [NormalizedPoint]
152
+ let mouth: [NormalizedPoint]
153
+ let outline: [NormalizedPoint]
154
+ let quality: FaceObservation.CaptureQuality?
155
+ }
198
156
 
199
- return DetectedFace(
200
- boundingBox: face.boundingBox,
201
- landmarks: landmarks,
202
- roll: face.roll,
203
- yaw: face.yaw,
204
- captureQuality: quality
157
+ func faceReports(in portrait: CGImage) async throws -> [FaceReport] {
158
+ let boxes = try await DetectFaceRectanglesRequest().perform(on: portrait)
159
+ let marks = try await DetectFaceLandmarksRequest().perform(on: portrait)
160
+ let scores = try await DetectFaceCaptureQualityRequest().perform(on: portrait)
161
+
162
+ return boxes.indices.map { i in
163
+ let landmarks = i < marks.count ? marks[i].landmarks : nil
164
+ return FaceReport(
165
+ box: boxes[i].boundingBox,
166
+ eyes: (landmarks?.leftEye.points ?? []) + (landmarks?.rightEye.points ?? []),
167
+ nose: landmarks?.nose.points ?? [],
168
+ mouth: landmarks?.outerLips.points ?? [],
169
+ outline: landmarks?.faceContour.points ?? [],
170
+ quality: i < scores.count ? scores[i].captureQuality : nil
205
171
  )
206
172
  }
207
173
  }
208
174
  ```
209
175
 
210
- ## Barcode Detection with All Symbologies
176
+ `FaceObservation.CaptureQuality` exposes `score`. To match faces reliably
177
+ instead of by index, set `inputFaceObservations` on the landmark or quality
178
+ request to the rectangles you already found.
179
+
180
+ ## Barcodes
211
181
 
212
182
  ```swift
213
183
  import Vision
214
184
 
215
- struct DetectedBarcode: Sendable {
185
+ struct ScannedCode: Sendable {
216
186
  let payload: String?
217
- let symbology: VNBarcodeSymbology
218
- let boundingBox: CGRect
187
+ let kind: BarcodeSymbology
188
+ let box: NormalizedRect
219
189
  }
220
190
 
221
- func detectBarcodes(
222
- in cgImage: CGImage,
223
- symbologies: [VNBarcodeSymbology] = [.qr, .ean13, .code128]
224
- ) async throws -> [DetectedBarcode] {
191
+ func codes(in label: CGImage,
192
+ accepting kinds: [BarcodeSymbology] = [.qr, .ean13, .code128]) async throws -> [ScannedCode] {
225
193
  var request = DetectBarcodesRequest()
226
- request.symbologies = symbologies
227
-
228
- let observations = try await request.perform(on: cgImage)
229
- return observations.map { barcode in
230
- DetectedBarcode(
231
- payload: barcode.payloadString,
232
- symbology: barcode.symbology,
233
- boundingBox: barcode.boundingBox
234
- )
194
+ request.symbologies = kinds
195
+ return try await request.perform(on: label).map {
196
+ ScannedCode(payload: $0.payloadString, kind: $0.symbology, box: $0.boundingBox)
235
197
  }
236
198
  }
237
199
 
238
- // Detect only QR codes with URL content
239
- func detectQRCodes(in cgImage: CGImage) async throws -> [URL] {
200
+ func firstLink(in poster: CGImage) async throws -> URL? {
240
201
  var request = DetectBarcodesRequest()
241
202
  request.symbologies = [.qr]
242
-
243
- let observations = try await request.perform(on: cgImage)
244
- return observations.compactMap { barcode in
245
- guard let payload = barcode.payloadString else { return nil }
246
- return URL(string: payload)
247
- }
203
+ let payload = try await request.perform(on: poster).first?.payloadString
204
+ return payload.flatMap(URL.init(string:))
248
205
  }
249
206
  ```
250
207
 
251
- ### Supported Symbologies Reference
208
+ The Swift-native type is `BarcodeSymbology`; the legacy and VisionKit APIs use
209
+ `VNBarcodeSymbology`. Both carry the same names.
252
210
 
253
- ```swift
254
- // 1D barcodes
255
- let linearSymbologies: [VNBarcodeSymbology] = [
256
- .codabar, .code39, .code39Checksum, .code39FullASCII,
257
- .code39FullASCIIChecksum, .code93, .code93i, .code128,
258
- .ean8, .ean13, .gs1DataBar, .gs1DataBarExpanded,
259
- .gs1DataBarLimited, .i2of5, .i2of5Checksum, .itf14,
260
- .msiPlessey, .upce,
261
- ]
262
-
263
- // 2D barcodes
264
- let matrixSymbologies: [VNBarcodeSymbology] = [
265
- .qr, .aztec, .dataMatrix, .pdf417, .microPDF417, .microQR,
266
- ]
267
- ```
211
+ Two-dimensional: `.aztec`, `.dataMatrix`, `.microPDF417`, `.microQR`,
212
+ `.pdf417`, `.qr`.
268
213
 
269
- ## Person Segmentation with Mask Application
214
+ One-dimensional: `.codabar`, `.code128`, `.code39`, `.code39Checksum`,
215
+ `.code39FullASCII`, `.code39FullASCIIChecksum`, `.code93`, `.code93i`,
216
+ `.ean13`, `.ean8`, `.gs1DataBar`, `.gs1DataBarExpanded`, `.gs1DataBarLimited`,
217
+ `.i2of5`, `.i2of5Checksum`, `.itf14`, `.msiPlessey`, `.upce`.
270
218
 
271
- ### Modern API (iOS 18+)
219
+ `supportedSymbologies` on the request lists what the current revision can read.
220
+
221
+ ## Person segmentation and background blur
222
+
223
+ The mask comes back smaller than the photo, so scale it to the photo's extent
224
+ before blending.
272
225
 
273
226
  ```swift
274
- import Vision
275
227
  import CoreImage
276
228
  import CoreImage.CIFilterBuiltins
229
+ import Vision
277
230
 
278
- func segmentPerson(in cgImage: CGImage) async throws -> CIImage {
279
- var request = GeneratePersonSegmentationRequest()
280
- request.qualityLevel = .accurate // .balanced, .fast
281
-
282
- let observation = try await request.perform(on: cgImage)
283
- let maskBuffer = observation.pixelBuffer
231
+ func personMask(for photo: CGImage) async throws -> CIImage {
232
+ let request = GeneratePersonSegmentationRequest()
233
+ request.qualityLevel = .balanced
234
+ let segmentation = try await request.perform(on: photo)
235
+ let mask = CIImage(cgImage: try segmentation.cgImage)
236
+ let sx = CGFloat(photo.width) / mask.extent.width
237
+ let sy = CGFloat(photo.height) / mask.extent.height
238
+ return mask.transformed(by: CGAffineTransform(scaleX: sx, y: sy))
239
+ }
284
240
 
285
- let originalImage = CIImage(cgImage: cgImage)
286
- let maskImage = CIImage(cvPixelBuffer: maskBuffer)
241
+ func portraitBlur(_ photo: CGImage, blurRadius: Float = 18) async throws -> CIImage {
242
+ let original = CIImage(cgImage: photo)
243
+ let mask = try await personMask(for: photo)
287
244
 
288
- // Scale mask to match original image size
289
- let scaleX = originalImage.extent.width / maskImage.extent.width
290
- let scaleY = originalImage.extent.height / maskImage.extent.height
291
- let scaledMask = maskImage.transformed(by: CGAffineTransform(
292
- scaleX: scaleX, y: scaleY
293
- ))
245
+ let blur = CIFilter.gaussianBlur()
246
+ blur.inputImage = original
247
+ blur.radius = blurRadius
248
+ let softened = blur.outputImage?.cropped(to: original.extent) ?? original
294
249
 
295
- return scaledMask
250
+ let blend = CIFilter.blendWithMask()
251
+ blend.inputImage = original
252
+ blend.backgroundImage = softened
253
+ blend.maskImage = mask
254
+ return blend.outputImage ?? original
296
255
  }
256
+ ```
297
257
 
298
- // Apply background blur using person mask
299
- func blurBackground(of cgImage: CGImage, blurRadius: Double = 20.0) async throws -> CIImage {
300
- let mask = try await segmentPerson(in: cgImage)
301
- let original = CIImage(cgImage: cgImage)
302
-
303
- let blurFilter = CIFilter.gaussianBlur()
304
- blurFilter.inputImage = original
305
- blurFilter.radius = Float(blurRadius)
306
- guard let blurredImage = blurFilter.outputImage else {
307
- throw SegmentationError.noMask
308
- }
258
+ The person stays sharp because the original is the input image and the blurred
259
+ copy is the background.
309
260
 
310
- let blendFilter = CIFilter.blendWithMask()
311
- blendFilter.inputImage = original // foreground (person)
312
- blendFilter.backgroundImage = blurredImage // blurred background
313
- blendFilter.maskImage = mask
261
+ Legacy mask (iOS 15+):
314
262
 
315
- guard let result = blendFilter.outputImage else {
316
- throw SegmentationError.noMask
317
- }
318
- return result
319
- }
263
+ ```swift
264
+ import Vision
320
265
 
321
- enum SegmentationError: Error {
322
- case noMask
266
+ enum MaskError: Error {
267
+ case empty
323
268
  }
324
- ```
325
-
326
- ### Legacy API (Pre-iOS 18)
327
269
 
328
- ```swift
329
- func segmentPersonLegacy(in cgImage: CGImage) throws -> CVPixelBuffer {
270
+ func legacyPersonMask(for photo: CGImage) throws -> CVPixelBuffer {
330
271
  let request = VNGeneratePersonSegmentationRequest()
331
272
  request.qualityLevel = .accurate
332
273
  request.outputPixelFormat = kCVPixelFormatType_OneComponent8
333
-
334
- let handler = VNImageRequestHandler(cgImage: cgImage)
335
- try handler.perform([request])
336
-
337
- guard let maskBuffer = request.results?.first?.pixelBuffer else {
338
- throw SegmentationError.noMask
274
+ try VNImageRequestHandler(cgImage: photo).perform([request])
275
+ guard let first = request.results?.first else {
276
+ throw MaskError.empty
339
277
  }
340
- return maskBuffer
278
+ return first.pixelBuffer
341
279
  }
342
280
  ```
343
281
 
344
- ### Instance Segmentation (iOS 18+)
345
-
346
- Separate masks per person for individual effects.
282
+ ### One mask per person
347
283
 
348
284
  ```swift
349
- // Modern API (iOS 18+)
350
- func segmentIndividualPeople(in cgImage: CGImage) async throws -> [CVPixelBuffer] {
351
- let request = GeneratePersonInstanceMaskRequest()
352
- let observation = try await request.perform(on: cgImage)
353
-
354
- let indices = observation.allInstances
355
- return try indices.map { index in
356
- try observation.generateMask(forInstances: IndexSet(integer: index))
357
- }
285
+ import Vision
286
+
287
+ func individualMasks(in groupShot: CGImage) async throws -> [CVPixelBuffer] {
288
+ guard let result = try await GeneratePersonInstanceMaskRequest().perform(on: groupShot) else { return [] }
289
+ return try result.allInstances.map { try result.generateMask(for: IndexSet(integer: $0)) }
358
290
  }
359
- ```
360
291
 
361
- ```swift
362
- // Legacy API (iOS 17+)
363
- func segmentIndividualPeopleLegacy(in cgImage: CGImage) throws -> [CVPixelBuffer] {
292
+ func legacyIndividualMasks(in groupShot: CGImage) throws -> [CVPixelBuffer] {
364
293
  let request = VNGeneratePersonInstanceMaskRequest()
365
- let handler = VNImageRequestHandler(cgImage: cgImage)
366
- try handler.perform([request])
367
-
368
- guard let result = request.results?.first else { return [] }
369
- let indices = result.allInstances
370
-
371
- return try indices.map { index in
372
- try result.generateMask(forInstances: IndexSet(integer: index))
294
+ try VNImageRequestHandler(cgImage: groupShot).perform([request])
295
+ guard let people = request.results?.first else {
296
+ return []
297
+ }
298
+ return try people.allInstances.map { person in
299
+ try people.generateMask(forInstances: [person])
373
300
  }
374
301
  }
375
302
  ```
376
303
 
377
- ## Image Classification
304
+ The legacy request needs iOS 17. The Swift-native observation also offers
305
+ `generateMaskedImage(for:imageFrom:croppedToInstancesExtent:)` when you want the
306
+ cut-out pixels rather than a mask.
307
+
308
+ ## Classification
378
309
 
379
310
  ```swift
380
311
  import Vision
381
312
 
382
- func classifyImage(_ cgImage: CGImage, maxResults: Int = 5) async throws -> [(String, Float)] {
383
- let request = ClassifyImageRequest()
384
- let observations = try await request.perform(on: cgImage)
385
-
386
- return observations.prefix(maxResults).map { observation in
387
- (observation.identifier, observation.confidence)
388
- }
313
+ func sceneTags(for picture: CGImage, limit: Int = 5) async throws -> [(label: String, score: Float)] {
314
+ let labels = try await ClassifyImageRequest().perform(on: picture)
315
+ return labels.prefix(limit).map { ($0.identifier, $0.confidence) }
389
316
  }
390
317
  ```
391
318
 
392
- ## Saliency Detection
319
+ ## Saliency
393
320
 
394
- Identify the most visually important or attention-grabbing regions.
321
+ Attention-based saliency predicts where people look. Objectness-based saliency
322
+ marks things that look like distinct objects.
395
323
 
396
324
  ```swift
397
- // Attention-based saliency (what humans would look at)
398
- func detectAttentionSaliency(in cgImage: CGImage) async throws -> [CGRect] {
399
- let request = GenerateAttentionBasedSaliencyImageRequest()
400
- let results = try await request.perform(on: cgImage)
401
- guard let saliency = results.first else { return [] }
402
- return saliency.salientObjects?.map(\.boundingBox) ?? []
325
+ import Vision
326
+
327
+ func focusRegions(in picture: CGImage) async throws -> [NormalizedRect] {
328
+ let heat = try await GenerateAttentionBasedSaliencyImageRequest().perform(on: picture)
329
+ return heat.salientObjects.map(\.boundingBox)
403
330
  }
404
331
 
405
- // Objectness-based saliency (distinct objects)
406
- func detectObjectSaliency(in cgImage: CGImage) async throws -> [CGRect] {
407
- let request = GenerateObjectnessBasedSaliencyImageRequest()
408
- let results = try await request.perform(on: cgImage)
409
- guard let saliency = results.first else { return [] }
410
- return saliency.salientObjects?.map(\.boundingBox) ?? []
332
+ func objectRegions(in picture: CGImage) async throws -> [NormalizedRect] {
333
+ let heat = try await GenerateObjectnessBasedSaliencyImageRequest().perform(on: picture)
334
+ return heat.salientObjects.map(\.boundingBox)
411
335
  }
412
336
  ```
413
337
 
414
- ## Rectangle Detection
338
+ `salientObjects` is a non-optional array in the Swift-native API; the legacy
339
+ `VNSaliencyImageObservation.salientObjects` is optional. The heat map itself is
340
+ `heat.heatMap`.
415
341
 
416
- Detect rectangular shapes for document edges, business cards, etc.
342
+ ## Rectangles
417
343
 
418
344
  ```swift
419
- func detectRectangles(in cgImage: CGImage) async throws -> [CGRect] {
420
- var request = DetectRectanglesRequest()
421
- request.minimumAspectRatio = 0.3
422
- request.maximumAspectRatio = 1.0
423
- request.minimumSize = 0.1
424
- request.maximumObservations = 5
345
+ import Vision
425
346
 
426
- let observations = try await request.perform(on: cgImage)
427
- return observations.map(\.boundingBox)
347
+ func cardOutlines(in picture: CGImage) async throws -> [NormalizedRect] {
348
+ var request = DetectRectanglesRequest()
349
+ request.minimumAspectRatio = 0.5
350
+ request.maximumAspectRatio = 0.8
351
+ request.minimumSize = 0.2
352
+ request.maximumObservations = 3
353
+ return try await request.perform(on: picture).map(\.boundingBox)
428
354
  }
429
355
  ```
430
356
 
431
- ## Horizon Detection
357
+ Aspect-ratio bounds filter shapes (a payment card is about 0.63), `minimumSize`
358
+ is a fraction of the image, and `maximumObservations` caps the result count.
359
+ Each `RectangleObservation` also exposes its four corners.
432
360
 
433
- Detect the horizon angle for auto-straightening photos.
361
+ ## Horizon
434
362
 
435
363
  ```swift
436
- func detectHorizon(in cgImage: CGImage) async throws -> CGFloat? {
437
- let request = DetectHorizonRequest()
438
- let results = try await request.perform(on: cgImage)
439
- return results.first?.angle.map { CGFloat($0) }
364
+ import Vision
365
+
366
+ func tiltCorrection(for picture: CGImage) async throws -> CGFloat? {
367
+ guard let horizon = try await DetectHorizonRequest().perform(on: picture) else { return nil }
368
+ return CGFloat(-horizon.angle.converted(to: .radians).value)
440
369
  }
441
370
  ```
442
371
 
443
- ## Batch Processing Multiple Requests
372
+ `angle` is a `Measurement<UnitAngle>`; convert it before using it as a
373
+ rotation. `horizon.transform(for:)` gives a ready-made affine transform.
374
+
375
+ ## Several requests on one image
444
376
 
445
- Run multiple requests on the same image simultaneously for efficiency.
377
+ Swift-native requests are independent values, so run them concurrently:
446
378
 
447
379
  ```swift
448
- func analyzeImage(_ cgImage: CGImage) async throws -> ImageAnalysisResult {
449
- async let textResults = {
450
- var req = RecognizeTextRequest()
451
- req.recognitionLevel = .accurate
452
- return try await req.perform(on: cgImage)
453
- }()
454
-
455
- async let faceResults = {
456
- let req = DetectFaceRectanglesRequest()
457
- return try await req.perform(on: cgImage)
458
- }()
459
-
460
- async let barcodeResults = {
461
- var req = DetectBarcodesRequest()
462
- req.symbologies = [.qr, .ean13]
463
- return try await req.perform(on: cgImage)
464
- }()
465
-
466
- let text = try await textResults
467
- let faces = try await faceResults
468
- let barcodes = try await barcodeResults
469
-
470
- return ImageAnalysisResult(
471
- recognizedText: text.compactMap { $0.topCandidates(1).first?.string },
472
- faceCount: faces.count,
473
- barcodePayloads: barcodes.compactMap(\.payloadString)
474
- )
475
- }
380
+ import Vision
476
381
 
477
- struct ImageAnalysisResult: Sendable {
478
- let recognizedText: [String]
382
+ struct PhotoDigest: Sendable {
383
+ let lines: [String]
479
384
  let faceCount: Int
480
- let barcodePayloads: [String]
385
+ let links: [String]
386
+ }
387
+
388
+ func digest(of picture: CGImage) async throws -> PhotoDigest {
389
+ async let text = RecognizeTextRequest().perform(on: picture)
390
+ async let faces = DetectFaceRectanglesRequest().perform(on: picture)
391
+ async let codes = DetectBarcodesRequest().perform(on: picture)
392
+
393
+ return PhotoDigest(
394
+ lines: try await text.compactMap(\.bestString),
395
+ faceCount: try await faces.count,
396
+ links: try await codes.compactMap(\.payloadString)
397
+ )
481
398
  }
482
399
  ```
483
400
 
484
- ### Legacy Batch Processing
401
+ `ImageRequestHandler(picture).perform(a, b, c)` is an alternative that shares
402
+ one decoded image across requests and returns a tuple of typed results.
485
403
 
486
- With the legacy API, pass multiple requests to a single handler call.
404
+ With the legacy API, give one handler all requests at once:
487
405
 
488
406
  ```swift
489
- func analyzeImageLegacy(_ cgImage: CGImage) throws {
490
- let textRequest = VNRecognizeTextRequest { request, error in
491
- // Handle text results
492
- }
493
- let faceRequest = VNDetectFaceRectanglesRequest { request, error in
494
- // Handle face results
495
- }
496
- let barcodeRequest = VNDetectBarcodesRequest { request, error in
497
- // Handle barcode results
498
- }
407
+ import Vision
499
408
 
500
- let handler = VNImageRequestHandler(cgImage: cgImage)
501
- try handler.perform([textRequest, faceRequest, barcodeRequest])
409
+ func legacyDigest(of picture: CGImage) throws -> (VNRecognizeTextRequest, VNDetectFaceRectanglesRequest, VNDetectBarcodesRequest) {
410
+ let text = VNRecognizeTextRequest()
411
+ let faces = VNDetectFaceRectanglesRequest()
412
+ let codes = VNDetectBarcodesRequest()
413
+ try VNImageRequestHandler(cgImage: picture).perform([text, faces, codes])
414
+ return (text, faces, codes)
502
415
  }
503
416
  ```
504
417
 
505
- ## Video Frame Processing with CMSampleBuffer
418
+ ## Camera frames from AVFoundation
506
419
 
507
- Process live camera frames from AVCaptureSession.
420
+ The capture delegate runs on its own serial queue and hands out non-Sendable
421
+ `CMSampleBuffer` values. Forward the newest frame into an `AsyncStream` and do
422
+ the Vision work in a consumer task; the stream's buffering policy drops frames
423
+ while one is being processed.
508
424
 
509
425
  ```swift
510
426
  import AVFoundation
511
427
  import Vision
512
428
 
513
- final class VisionVideoProcessor: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, Sendable {
514
- private let processingQueue = DispatchQueue(label: "vision.processing", qos: .userInitiated)
429
+ struct CameraFrame: @unchecked Sendable {
430
+ let sample: CMSampleBuffer
431
+ }
515
432
 
516
- func setupCapture(session: AVCaptureSession) {
517
- let output = AVCaptureVideoDataOutput()
518
- output.setSampleBufferDelegate(self, queue: processingQueue)
519
- output.alwaysDiscardsLateVideoFrames = true
433
+ final class FramePump: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
434
+ let frames: AsyncStream<CameraFrame>
435
+ private let sink: AsyncStream<CameraFrame>.Continuation
436
+ private let output = AVCaptureVideoDataOutput()
437
+ private let queue = DispatchQueue(label: "camera.frames", qos: .userInitiated)
438
+
439
+ override init() {
440
+ let pair = AsyncStream.makeStream(of: CameraFrame.self, bufferingPolicy: .bufferingNewest(1))
441
+ frames = pair.stream
442
+ sink = pair.continuation
443
+ super.init()
444
+ }
520
445
 
446
+ func attach(to session: AVCaptureSession) {
447
+ output.setSampleBufferDelegate(self, queue: queue)
448
+ output.alwaysDiscardsLateVideoFrames = true
521
449
  if session.canAddOutput(output) {
522
450
  session.addOutput(output)
523
451
  }
524
452
  }
525
453
 
526
- func captureOutput(
527
- _ output: AVCaptureOutput,
528
- didOutput sampleBuffer: CMSampleBuffer,
529
- from connection: AVCaptureConnection
530
- ) {
531
- Task {
532
- do {
533
- var request = RecognizeTextRequest()
534
- request.recognitionLevel = .fast
535
- let observations = try await request.perform(on: sampleBuffer)
536
- let strings = observations.compactMap {
537
- $0.topCandidates(1).first?.string
538
- }
539
- // Dispatch results to main actor for UI update
540
- await MainActor.run {
541
- // Update UI with recognized strings
542
- }
543
- } catch {
544
- // Handle error
545
- }
546
- }
454
+ func captureOutput(_ output: AVCaptureOutput,
455
+ didOutput sampleBuffer: CMSampleBuffer,
456
+ from connection: AVCaptureConnection) {
457
+ sink.yield(CameraFrame(sample: sampleBuffer))
458
+ }
459
+ }
460
+
461
+ func readLive(from pump: FramePump, show: @escaping @MainActor ([String]) -> Void) async {
462
+ var request = RecognizeTextRequest()
463
+ request.recognitionLevel = .fast
464
+ for await frame in pump.frames {
465
+ guard let found = try? await request.perform(on: frame.sample) else { continue }
466
+ let lines = found.compactMap(\.bestString)
467
+ await MainActor.run { show(lines) }
547
468
  }
548
469
  }
549
470
  ```
550
471
 
551
- ### Object Tracking Across Video Frames
472
+ The `@unchecked Sendable` wrapper is acceptable here because the frame is
473
+ never touched again after it is yielded.
552
474
 
553
- #### Modern API (iOS 18+)
475
+ ## Tracking across frames
554
476
 
555
- `TrackObjectRequest` is a stateful request that maintains tracking context
556
- internally. No need for a separate sequence handler.
477
+ The Swift-native tracker keeps its own history, so there is no sequence
478
+ handler to manage. Keep the request object for as long as you track.
557
479
 
558
480
  ```swift
559
481
  import Vision
560
482
 
561
- final class ObjectTracker {
483
+ final class SubjectFollower {
562
484
  private var request: TrackObjectRequest?
485
+ private let minimumConfidence: Float = 0.3
563
486
 
564
- /// Initialize tracking with a bounding box in normalized coordinates
565
- func startTracking(boundingBox: CGRect) {
566
- let observation = DetectedObjectObservation(boundingBox: boundingBox)
567
- var req = TrackObjectRequest(observation: observation)
568
- req.trackingLevel = .accurate
569
- request = req
487
+ func begin(at box: NormalizedRect) {
488
+ request = TrackObjectRequest(detectedObject: DetectedObjectObservation(boundingBox: box))
570
489
  }
571
490
 
572
- /// Track object in next video frame
573
- func track(in pixelBuffer: CVPixelBuffer) async throws -> CGRect? {
574
- guard var req = request else { return nil }
575
-
576
- let results = try await req.perform(on: pixelBuffer)
577
- guard let tracked = results.first, tracked.confidence > 0.3 else {
578
- request = nil
491
+ func next(_ frame: CVPixelBuffer) async throws -> NormalizedRect? {
492
+ guard let request else { return nil }
493
+ guard let hit = try await request.perform(on: frame), hit.confidence > minimumConfidence else {
494
+ end()
579
495
  return nil
580
496
  }
581
-
582
- request = req // preserve stateful tracking context
583
- return tracked.boundingBox
497
+ return hit.boundingBox
584
498
  }
585
499
 
586
- func stopTracking() {
500
+ func end() {
587
501
  request = nil
588
502
  }
589
503
  }
590
504
  ```
591
505
 
592
- #### Legacy API
506
+ Legacy tracker with an explicit sequence handler:
593
507
 
594
508
  ```swift
595
- final class LegacyObjectTracker {
596
- private var sequenceHandler = VNSequenceRequestHandler()
597
- private var currentObservation: VNDetectedObjectObservation?
598
-
599
- func startTracking(boundingBox: CGRect) {
600
- currentObservation = VNDetectedObjectObservation(boundingBox: boundingBox)
601
- }
602
-
603
- func track(in pixelBuffer: CVPixelBuffer) throws -> CGRect? {
604
- guard let observation = currentObservation else { return nil }
509
+ import Vision
605
510
 
606
- let trackRequest = VNTrackObjectRequest(detectedObjectObservation: observation)
607
- trackRequest.trackingLevel = .accurate
511
+ final class LegacySubjectFollower {
512
+ private let sequence = VNSequenceRequestHandler()
513
+ private var last: VNDetectedObjectObservation?
608
514
 
609
- try sequenceHandler.perform([trackRequest], on: pixelBuffer)
515
+ func begin(at box: CGRect) {
516
+ last = VNDetectedObjectObservation(boundingBox: box)
517
+ }
610
518
 
611
- guard let result = trackRequest.results?.first as? VNDetectedObjectObservation,
612
- result.confidence > 0.3 else {
613
- currentObservation = nil
519
+ func next(_ frame: CVPixelBuffer) throws -> CGRect? {
520
+ guard let seed = last else { return nil }
521
+ let request = VNTrackObjectRequest(detectedObjectObservation: seed)
522
+ request.trackingLevel = .accurate
523
+ try sequence.perform([request], on: frame)
524
+ guard let hit = request.results?.first as? VNDetectedObjectObservation, hit.confidence > 0.3 else {
525
+ last = nil
614
526
  return nil
615
527
  }
616
-
617
- currentObservation = result
618
- return result.boundingBox
619
- }
620
-
621
- func stopTracking() {
622
- currentObservation = nil
528
+ last = hit
529
+ return hit.boundingBox
623
530
  }
624
531
  }
625
532
  ```
626
533
 
627
- ## Coordinate Normalization Utilities
534
+ ## Coordinate helpers
535
+
536
+ Normalized values run from 0 to 1 with the origin at the lower left.
628
537
 
629
- Vision uses normalized coordinates (0...1) with bottom-left origin. These
630
- utilities convert to UIKit/SwiftUI coordinate systems.
538
+ - `VNImageRectForNormalizedRect(rect, width, height)` scales a normalized
539
+ rectangle to pixels.
540
+ - `VNImagePointForNormalizedPoint(point, width, height)` does the same for a
541
+ point.
542
+ - Neither flips the y axis; UIKit drawing still needs a flip.
543
+ - Swift-native `NormalizedRect` and `NormalizedPoint` have
544
+ `toImageCoordinates(_:origin:)` which scales and, with `.upperLeft`, flips.
631
545
 
632
546
  ```swift
633
547
  import Vision
634
- import UIKit
635
-
636
- enum VisionCoordinateConverter {
637
- /// Convert normalized Vision rect to image-pixel coordinates
638
- static func toImageCoordinates(
639
- _ normalizedRect: CGRect,
640
- imageWidth: Int,
641
- imageHeight: Int
642
- ) -> CGRect {
643
- VNImageRectForNormalizedRect(normalizedRect, imageWidth, imageHeight)
644
- }
645
548
 
646
- /// Convert normalized Vision point to image-pixel coordinates
647
- static func toImageCoordinates(
648
- _ normalizedPoint: CGPoint,
649
- imageWidth: Int,
650
- imageHeight: Int
651
- ) -> CGPoint {
652
- VNImagePointForNormalizedPoint(normalizedPoint, imageWidth, imageHeight)
653
- }
654
-
655
- /// Convert Vision rect (bottom-left origin) to UIKit rect (top-left origin)
656
- static func toUIKitCoordinates(
657
- _ normalizedRect: CGRect,
658
- viewSize: CGSize
659
- ) -> CGRect {
660
- let imageRect = VNImageRectForNormalizedRect(
661
- normalizedRect,
662
- Int(viewSize.width),
663
- Int(viewSize.height)
664
- )
665
- // Flip Y axis: Vision origin is bottom-left, UIKit is top-left
666
- return CGRect(
667
- x: imageRect.origin.x,
668
- y: viewSize.height - imageRect.origin.y - imageRect.height,
669
- width: imageRect.width,
670
- height: imageRect.height
671
- )
672
- }
549
+ func viewRect(for normalized: CGRect, in viewSize: CGSize) -> CGRect {
550
+ let pixelWidth = Int(viewSize.width)
551
+ let pixelHeight = Int(viewSize.height)
552
+ let scaled = VNImageRectForNormalizedRect(normalized, pixelWidth, pixelHeight)
553
+ return CGRect(x: scaled.minX,
554
+ y: viewSize.height - scaled.minY - scaled.height,
555
+ width: scaled.width,
556
+ height: scaled.height)
557
+ }
673
558
 
674
- /// Convert an array of normalized points to UIKit points
675
- static func toUIKitPoints(
676
- _ normalizedPoints: [CGPoint],
677
- viewSize: CGSize
678
- ) -> [CGPoint] {
679
- normalizedPoints.map { point in
680
- CGPoint(
681
- x: point.x * viewSize.width,
682
- y: (1.0 - point.y) * viewSize.height // flip Y
683
- )
684
- }
559
+ func viewPoints(for normalized: [CGPoint], in viewSize: CGSize) -> [CGPoint] {
560
+ normalized.map { p in
561
+ CGPoint(x: p.x * viewSize.width, y: viewSize.height - p.y * viewSize.height)
685
562
  }
686
563
  }
687
564
  ```
688
565
 
689
- ## Performance Considerations
566
+ ## Performance
690
567
 
691
- ### Recognition Level Selection
568
+ Ballpark costs; measure on the oldest device you support.
692
569
 
693
- | Use Case | Level | Typical Latency |
570
+ | Scenario | Level | Typical cost |
694
571
  |---|---|---|
695
- | Live camera preview | `.fast` | ~30ms per frame |
696
- | Photo library scan | `.accurate` | ~200-500ms per image |
697
- | Batch document OCR | `.accurate` | ~200-500ms per page |
698
- | Barcode scanner | `.fast` or `.balanced` | ~15-50ms per frame |
572
+ | Live camera text | `.fast` | about 30 ms per frame |
573
+ | Photo library scan | `.accurate` | 200 to 500 ms per image |
574
+ | Batch document OCR | `.accurate` | 200 to 500 ms per page |
575
+ | Barcode scanning | `.fast` or `.balanced` | 15 to 50 ms per frame |
699
576
 
700
- ### Memory Management
577
+ Memory:
701
578
 
702
- - Reuse `VNSequenceRequestHandler` across video frames (do not recreate per frame)
703
- - For batch processing, process one image at a time to avoid memory spikes
704
- - Release `CVPixelBuffer` references promptly after processing
705
- - Use `autoreleasepool` in tight loops processing many images
579
+ - Reuse a single `VNSequenceRequestHandler` for a whole frame sequence.
580
+ - Work through large photo sets one image at a time so memory does not spike:
706
581
 
707
582
  ```swift
708
- func batchProcess(images: [CGImage]) async throws -> [[String]] {
709
- var allResults: [[String]] = []
583
+ import Vision
710
584
 
711
- for image in images {
585
+ func transcribeAll(_ pages: [CGImage]) async throws -> [[String]] {
586
+ var output: [[String]] = []
587
+ for page in pages {
712
588
  var request = RecognizeTextRequest()
713
589
  request.recognitionLevel = .accurate
714
- let obs = try await request.perform(on: image)
715
- let result = obs.compactMap { $0.topCandidates(1).first?.string }
716
- allResults.append(result)
590
+ let found = try await request.perform(on: page)
591
+ output.append(found.compactMap(\.bestString))
717
592
  }
718
- return allResults
593
+ return output
719
594
  }
720
595
  ```
721
596
 
722
- ### Threading
597
+ - Let go of `CVPixelBuffer` references as soon as you are done; wrap tight
598
+ synchronous loops over legacy handlers in `autoreleasepool`.
723
599
 
724
- - Modern API (`perform(on:)`) is async and safe to call from any context
725
- - Legacy API: create `VNImageRequestHandler` and call `perform` on a background queue
726
- - Never block the main thread with Vision requests
727
- - `VNSequenceRequestHandler` is not thread-safe -- use from a single serial queue
600
+ Threading:
728
601
 
729
- ### Request Reuse
602
+ - Swift-native `perform(on:)` is async and can be awaited from any context.
603
+ - Legacy `perform` is synchronous; call it from a background queue, never the
604
+ main thread.
605
+ - `VNSequenceRequestHandler` has no internal locking; confine each instance to
606
+ a single serial queue.
730
607
 
731
- Modern request structs are value types and cheap to create. Do not try to cache
732
- and reuse them across calls -- just create a fresh one each time.
608
+ Reuse:
733
609
 
734
- For the legacy API, `VNImageRequestHandler` is tied to a single image. Create a
735
- new handler for each image you process. `VNSequenceRequestHandler` can be reused
736
- across frames in a sequence.
610
+ - Swift-native request structs are cheap values; build a fresh one per job
611
+ rather than caching it. Stateful request classes are the exception: keep
612
+ them for the sequence they track.
613
+ - A `VNImageRequestHandler` belongs to one image; create one per image. A
614
+ `VNSequenceRequestHandler` is meant to be reused across a sequence.