@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.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,536 +1,358 @@
|
|
|
1
|
-
# VisionKit
|
|
1
|
+
# VisionKit scanners
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
All patterns target iOS 26+ with Swift 6.3 unless noted.
|
|
3
|
+
Camera UI that VisionKit provides on top of Vision: the live
|
|
4
|
+
`DataScannerViewController` and the paper-oriented
|
|
5
|
+
`VNDocumentCameraViewController`. Summary in `../SKILL.md`.
|
|
7
6
|
|
|
8
7
|
## Contents
|
|
9
|
-
- Camera Permission Setup
|
|
10
|
-
- DataScannerViewController
|
|
11
|
-
- Delegate Methods
|
|
12
|
-
- SwiftUI Integration
|
|
13
|
-
- Custom Overlay UI
|
|
14
|
-
- VNDocumentCameraViewController
|
|
15
8
|
|
|
16
|
-
|
|
9
|
+
- [Camera permission](#camera-permission)
|
|
10
|
+
- [DataScannerViewController](#datascannerviewcontroller)
|
|
11
|
+
- [Delegate callbacks](#delegate-callbacks)
|
|
12
|
+
- [SwiftUI](#swiftui)
|
|
13
|
+
- [Custom overlay](#custom-overlay)
|
|
14
|
+
- [VNDocumentCameraViewController](#vndocumentcameraviewcontroller)
|
|
15
|
+
- [Performance](#performance)
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
## Camera permission
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Request permission before presenting the scanner:
|
|
19
|
+
Both scanners use the camera, so Info.plist needs `NSCameraUsageDescription`
|
|
20
|
+
before either is shown. Ask for access explicitly when you want to control the
|
|
21
|
+
moment of the prompt:
|
|
26
22
|
|
|
27
23
|
```swift
|
|
28
24
|
import AVFoundation
|
|
29
25
|
|
|
30
|
-
func
|
|
26
|
+
func cameraAllowed() async -> Bool {
|
|
31
27
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
32
|
-
|
|
33
|
-
case .authorized:
|
|
28
|
+
if status == .authorized {
|
|
34
29
|
return true
|
|
35
|
-
case .notDetermined:
|
|
36
|
-
return await AVCaptureDevice.requestAccess(for: .video)
|
|
37
|
-
case .denied, .restricted:
|
|
38
|
-
return false
|
|
39
|
-
@unknown default:
|
|
40
|
-
return false
|
|
41
30
|
}
|
|
31
|
+
if status == .notDetermined {
|
|
32
|
+
let granted = await AVCaptureDevice.requestAccess(for: .video)
|
|
33
|
+
return granted
|
|
34
|
+
}
|
|
35
|
+
return false
|
|
42
36
|
}
|
|
43
37
|
```
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
`.denied`, `.restricted` and any status added in a future release all fall
|
|
40
|
+
through to `false`.
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
and barcodes with built-in highlighting and interaction. Available on devices
|
|
49
|
-
with an A12 chip or later (iOS 16+).
|
|
42
|
+
## DataScannerViewController
|
|
50
43
|
|
|
51
|
-
|
|
44
|
+
A full-screen live scanner with built-in highlighting and tap handling.
|
|
45
|
+
Available from iOS 16 on devices with an A12 Bionic chip or newer.
|
|
52
46
|
|
|
53
|
-
|
|
47
|
+
Two class properties gate it, and both must be true before presenting:
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
- `isSupported`: the hardware can run it.
|
|
50
|
+
- `isAvailable`: supported, plus camera access is granted and not blocked by
|
|
51
|
+
restrictions such as device management.
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
// Hardware check: requires A12 Bionic or later
|
|
60
|
-
guard DataScannerViewController.isSupported else {
|
|
61
|
-
return false
|
|
62
|
-
}
|
|
63
|
-
// Runtime check: camera authorized and not restricted
|
|
64
|
-
guard DataScannerViewController.isAvailable else {
|
|
65
|
-
return false
|
|
66
|
-
}
|
|
67
|
-
return true
|
|
68
|
-
}
|
|
69
|
-
```
|
|
53
|
+
### Initializer options
|
|
70
54
|
|
|
71
|
-
|
|
72
|
-
|
|
55
|
+
| Parameter | Default | Meaning |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `recognizedDataTypes` | required | Set of text and barcode types to find |
|
|
58
|
+
| `qualityLevel` | `.balanced` | Speed versus detail |
|
|
59
|
+
| `recognizesMultipleItems` | `false` | Track many items or only the most prominent |
|
|
60
|
+
| `isHighFrameRateTrackingEnabled` | `true` | Smoother highlight tracking, more power |
|
|
61
|
+
| `isPinchToZoomEnabled` | `true` | Pinch gesture zooms the camera |
|
|
62
|
+
| `isGuidanceEnabled` | `true` | On-screen hints such as "slow down" |
|
|
63
|
+
| `isHighlightingEnabled` | `false` | System highlight boxes around found items |
|
|
73
64
|
|
|
74
|
-
|
|
65
|
+
Three configurations for different jobs:
|
|
75
66
|
|
|
76
67
|
```swift
|
|
77
68
|
import VisionKit
|
|
78
69
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
70
|
+
@MainActor
|
|
71
|
+
enum ScannerPresets {
|
|
72
|
+
static func signReader() -> DataScannerViewController {
|
|
73
|
+
DataScannerViewController(
|
|
74
|
+
recognizedDataTypes: [.text()],
|
|
75
|
+
qualityLevel: .accurate,
|
|
76
|
+
recognizesMultipleItems: true,
|
|
77
|
+
isHighFrameRateTrackingEnabled: false,
|
|
78
|
+
isPinchToZoomEnabled: true,
|
|
79
|
+
isGuidanceEnabled: false,
|
|
80
|
+
isHighlightingEnabled: true
|
|
81
|
+
)
|
|
82
|
+
}
|
|
92
83
|
|
|
93
|
-
func
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
isPinchToZoomEnabled: false,
|
|
102
|
-
isGuidanceEnabled: true,
|
|
103
|
-
isHighlightingEnabled: true
|
|
104
|
-
)
|
|
105
|
-
}
|
|
84
|
+
static func boardingPassReader() -> DataScannerViewController {
|
|
85
|
+
DataScannerViewController(
|
|
86
|
+
recognizedDataTypes: [.barcode(symbologies: [.pdf417, .aztec, .qr])],
|
|
87
|
+
qualityLevel: .fast,
|
|
88
|
+
isHighFrameRateTrackingEnabled: false,
|
|
89
|
+
isPinchToZoomEnabled: false
|
|
90
|
+
)
|
|
91
|
+
}
|
|
106
92
|
|
|
107
|
-
func
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
isGuidanceEnabled: true,
|
|
118
|
-
isHighlightingEnabled: true
|
|
119
|
-
)
|
|
93
|
+
static func parcelReader() -> DataScannerViewController {
|
|
94
|
+
DataScannerViewController(
|
|
95
|
+
recognizedDataTypes: [
|
|
96
|
+
.text(textContentType: .shipmentTrackingNumber),
|
|
97
|
+
.barcode(symbologies: [.code128, .qr])
|
|
98
|
+
],
|
|
99
|
+
qualityLevel: .balanced,
|
|
100
|
+
isHighlightingEnabled: true
|
|
101
|
+
)
|
|
102
|
+
}
|
|
120
103
|
}
|
|
121
104
|
```
|
|
122
105
|
|
|
123
|
-
###
|
|
106
|
+
### Data types
|
|
124
107
|
|
|
125
|
-
|
|
126
|
-
// Text with language hints
|
|
127
|
-
let textType: DataScannerViewController.RecognizedDataType =
|
|
128
|
-
.text(languages: ["en", "fr", "de"])
|
|
129
|
-
|
|
130
|
-
// Text filtered by content type
|
|
131
|
-
let emailType: DataScannerViewController.RecognizedDataType =
|
|
132
|
-
.text(textContentType: .emailAddress)
|
|
133
|
-
let urlType: DataScannerViewController.RecognizedDataType =
|
|
134
|
-
.text(textContentType: .URL)
|
|
135
|
-
let phoneType: DataScannerViewController.RecognizedDataType =
|
|
136
|
-
.text(textContentType: .telephoneNumber)
|
|
137
|
-
let addressType: DataScannerViewController.RecognizedDataType =
|
|
138
|
-
.text(textContentType: .fullAddress)
|
|
139
|
-
let flightType: DataScannerViewController.RecognizedDataType =
|
|
140
|
-
.text(textContentType: .flightNumber)
|
|
141
|
-
let trackingType: DataScannerViewController.RecognizedDataType =
|
|
142
|
-
.text(textContentType: .shipmentTrackingNumber)
|
|
143
|
-
|
|
144
|
-
// Barcode with specific symbologies
|
|
145
|
-
let qrOnly: DataScannerViewController.RecognizedDataType =
|
|
146
|
-
.barcode(symbologies: [.qr])
|
|
147
|
-
let retailBarcodes: DataScannerViewController.RecognizedDataType =
|
|
148
|
-
.barcode(symbologies: [.ean8, .ean13, .upce, .code128])
|
|
149
|
-
```
|
|
108
|
+
`DataScannerViewController.RecognizedDataType` values:
|
|
150
109
|
|
|
151
|
-
|
|
110
|
+
- `.text(languages:textContentType:)`: both arguments are optional, so
|
|
111
|
+
`.text()`, `.text(languages: ["fr"])` and
|
|
112
|
+
`.text(textContentType: .flightNumber)` all work.
|
|
113
|
+
- `.barcode(symbologies:)`: takes `VNBarcodeSymbology` values.
|
|
152
114
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
| `.balanced` | General purpose text + barcode | Default choice |
|
|
157
|
-
| `.accurate` | Detailed OCR, small text | Higher latency |
|
|
115
|
+
Text content types narrow recognition to one kind of string: `.URL`,
|
|
116
|
+
`.emailAddress`, `.telephoneNumber`, `.fullStreetAddress`, `.flightNumber`,
|
|
117
|
+
`.shipmentTrackingNumber`, `.dateTimeDuration`, and `.currency` (iOS 17).
|
|
158
118
|
|
|
159
|
-
|
|
119
|
+
A retail product scanner usually needs `.ean13`, `.ean8`, `.upce` and
|
|
120
|
+
`.code128`.
|
|
160
121
|
|
|
161
|
-
|
|
162
|
-
func presentScanner(_ scanner: DataScannerViewController,
|
|
163
|
-
from presenter: UIViewController) {
|
|
164
|
-
scanner.delegate = presenter as? DataScannerViewControllerDelegate
|
|
165
|
-
presenter.present(scanner, animated: true) {
|
|
166
|
-
try? scanner.startScanning()
|
|
167
|
-
}
|
|
168
|
-
}
|
|
122
|
+
### Quality levels
|
|
169
123
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
```
|
|
124
|
+
- `.fast`: lowest latency; barcodes and large, quick text.
|
|
125
|
+
- `.balanced`: the sensible default for most scanners.
|
|
126
|
+
- `.accurate`: small or dense text; slower to respond.
|
|
175
127
|
|
|
176
|
-
|
|
128
|
+
### Start and stop
|
|
177
129
|
|
|
178
|
-
|
|
179
|
-
|
|
130
|
+
Scanning can only start once the controller is on screen. Set the delegate,
|
|
131
|
+
present, and start in the presentation completion. To finish, call
|
|
132
|
+
`stopScanning()` and then dismiss.
|
|
180
133
|
|
|
181
134
|
```swift
|
|
135
|
+
import UIKit
|
|
182
136
|
import VisionKit
|
|
183
137
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
var
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
switch item {
|
|
195
|
-
case .text(let text):
|
|
196
|
-
onTextRecognized?(text.transcript)
|
|
197
|
-
case .barcode(let barcode):
|
|
198
|
-
if let payload = barcode.payloadStringValue {
|
|
199
|
-
onBarcodeRecognized?(payload, barcode.observation.symbology)
|
|
200
|
-
}
|
|
201
|
-
@unknown default:
|
|
202
|
-
break
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Called when new items appear in the camera view
|
|
207
|
-
func dataScanner(
|
|
208
|
-
_ scanner: DataScannerViewController,
|
|
209
|
-
didAdd addedItems: [RecognizedItem],
|
|
210
|
-
allItems: [RecognizedItem]
|
|
211
|
-
) {
|
|
212
|
-
for item in addedItems {
|
|
213
|
-
switch item {
|
|
214
|
-
case .text(let text):
|
|
215
|
-
print("New text: \(text.transcript)")
|
|
216
|
-
case .barcode(let barcode):
|
|
217
|
-
print("New barcode: \(barcode.payloadStringValue ?? "nil")")
|
|
218
|
-
@unknown default:
|
|
219
|
-
break
|
|
220
|
-
}
|
|
138
|
+
@MainActor
|
|
139
|
+
final class ScanCoordinator {
|
|
140
|
+
private var live: DataScannerViewController?
|
|
141
|
+
|
|
142
|
+
func open(from host: UIViewController, delegate: DataScannerViewControllerDelegate) {
|
|
143
|
+
let passReader = ScannerPresets.boardingPassReader()
|
|
144
|
+
passReader.delegate = delegate
|
|
145
|
+
live = passReader
|
|
146
|
+
host.present(passReader, animated: true) {
|
|
147
|
+
try? passReader.startScanning()
|
|
221
148
|
}
|
|
222
149
|
}
|
|
223
150
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
) {
|
|
230
|
-
// Handle position or content updates
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Called when items leave the camera view
|
|
234
|
-
func dataScanner(
|
|
235
|
-
_ scanner: DataScannerViewController,
|
|
236
|
-
didRemove removedItems: [RecognizedItem],
|
|
237
|
-
allItems: [RecognizedItem]
|
|
238
|
-
) {
|
|
239
|
-
// Clean up UI for removed items
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Called when the scanner becomes unavailable (e.g., camera revoked)
|
|
243
|
-
func dataScannerDidChangeUnavailabilityReasons(
|
|
244
|
-
_ scanner: DataScannerViewController
|
|
245
|
-
) {
|
|
246
|
-
// Handle unavailability -- dismiss or show fallback
|
|
151
|
+
func close() {
|
|
152
|
+
guard let live else { return }
|
|
153
|
+
live.stopScanning()
|
|
154
|
+
live.dismiss(animated: true)
|
|
155
|
+
self.live = nil
|
|
247
156
|
}
|
|
248
157
|
}
|
|
249
158
|
```
|
|
250
159
|
|
|
251
|
-
|
|
160
|
+
## Delegate callbacks
|
|
161
|
+
|
|
162
|
+
`DataScannerViewControllerDelegate` methods, all main-actor isolated:
|
|
163
|
+
|
|
164
|
+
| Method | When it fires |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `dataScanner(_:didTapOn:)` | The user tapped a recognized item |
|
|
167
|
+
| `dataScanner(_:didAdd:allItems:)` | New items appeared |
|
|
168
|
+
| `dataScanner(_:didUpdate:allItems:)` | Items moved or their content changed |
|
|
169
|
+
| `dataScanner(_:didRemove:allItems:)` | Items left the frame |
|
|
170
|
+
| `dataScanner(_:becameUnavailableWithError:)` | Scanning stopped being possible, for example camera access was revoked; dismiss or fall back |
|
|
171
|
+
| `dataScannerDidZoom(_:)` | Zoom factor changed |
|
|
252
172
|
|
|
253
|
-
|
|
173
|
+
`RecognizedItem` is an enum. Handle both cases and keep an `@unknown default`:
|
|
254
174
|
|
|
255
175
|
```swift
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
await MainActor.run {
|
|
267
|
-
// Update UI with current texts and barcodes
|
|
268
|
-
}
|
|
176
|
+
import VisionKit
|
|
177
|
+
|
|
178
|
+
func describe(_ item: RecognizedItem) -> String {
|
|
179
|
+
switch item {
|
|
180
|
+
case .text(let recognized):
|
|
181
|
+
return recognized.transcript
|
|
182
|
+
case .barcode(let code):
|
|
183
|
+
return "\(code.observation.symbology.rawValue): \(code.payloadStringValue ?? "?")"
|
|
184
|
+
@unknown default:
|
|
185
|
+
return ""
|
|
269
186
|
}
|
|
270
187
|
}
|
|
271
188
|
```
|
|
272
189
|
|
|
273
|
-
###
|
|
190
|
+
### Async stream of items
|
|
274
191
|
|
|
275
|
-
|
|
192
|
+
`recognizedItems` is an `AsyncStream<[RecognizedItem]>`: every element is the
|
|
193
|
+
full set of items currently in view.
|
|
276
194
|
|
|
277
195
|
```swift
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
196
|
+
import VisionKit
|
|
197
|
+
|
|
198
|
+
@MainActor
|
|
199
|
+
func watch(_ scanner: DataScannerViewController,
|
|
200
|
+
onText: ([String]) -> Void,
|
|
201
|
+
onCodes: ([String]) -> Void) async {
|
|
202
|
+
for await visible in scanner.recognizedItems {
|
|
203
|
+
var lines: [String] = []
|
|
204
|
+
var codes: [String] = []
|
|
205
|
+
for item in visible {
|
|
206
|
+
switch item {
|
|
207
|
+
case .text(let recognized): lines.append(recognized.transcript)
|
|
208
|
+
case .barcode(let code): codes.append(code.payloadStringValue ?? "")
|
|
209
|
+
@unknown default: break
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
onText(lines)
|
|
213
|
+
onCodes(codes)
|
|
214
|
+
}
|
|
281
215
|
}
|
|
282
216
|
```
|
|
283
217
|
|
|
284
|
-
|
|
218
|
+
### Still photo
|
|
285
219
|
|
|
286
|
-
|
|
287
|
-
|
|
220
|
+
`try await scanner.capturePhoto()` returns a `UIImage` of the current camera
|
|
221
|
+
frame, useful for a high-resolution OCR pass or for attaching to a record.
|
|
288
222
|
|
|
289
|
-
|
|
223
|
+
## SwiftUI
|
|
224
|
+
|
|
225
|
+
A representable that reports tapped items through bindings:
|
|
290
226
|
|
|
291
227
|
```swift
|
|
292
|
-
import SwiftUI
|
|
293
228
|
import VisionKit
|
|
229
|
+
import SwiftUI
|
|
294
230
|
|
|
295
|
-
struct
|
|
296
|
-
let
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
@Binding var
|
|
300
|
-
@Binding var
|
|
231
|
+
struct LiveScanner: UIViewControllerRepresentable {
|
|
232
|
+
let kinds: Set<DataScannerViewController.RecognizedDataType>
|
|
233
|
+
var quality: DataScannerViewController.QualityLevel = .balanced
|
|
234
|
+
var many = false
|
|
235
|
+
@Binding var lines: [String]
|
|
236
|
+
@Binding var payloads: [String]
|
|
301
237
|
|
|
302
238
|
func makeUIViewController(context: Context) -> DataScannerViewController {
|
|
303
|
-
let
|
|
304
|
-
recognizedDataTypes:
|
|
305
|
-
qualityLevel:
|
|
306
|
-
recognizesMultipleItems:
|
|
307
|
-
isHighFrameRateTrackingEnabled: true,
|
|
308
|
-
isPinchToZoomEnabled: true,
|
|
309
|
-
isGuidanceEnabled: true,
|
|
239
|
+
let liveView = DataScannerViewController(
|
|
240
|
+
recognizedDataTypes: kinds,
|
|
241
|
+
qualityLevel: quality,
|
|
242
|
+
recognizesMultipleItems: many,
|
|
310
243
|
isHighlightingEnabled: true
|
|
311
244
|
)
|
|
312
|
-
|
|
313
|
-
|
|
245
|
+
liveView.delegate = context.coordinator
|
|
246
|
+
Task { @MainActor in
|
|
247
|
+
try? liveView.startScanning()
|
|
248
|
+
}
|
|
249
|
+
return liveView
|
|
314
250
|
}
|
|
315
251
|
|
|
316
|
-
func updateUIViewController(
|
|
317
|
-
_ controller: DataScannerViewController,
|
|
318
|
-
context: Context
|
|
319
|
-
) {
|
|
320
|
-
// No dynamic updates needed
|
|
321
|
-
}
|
|
252
|
+
func updateUIViewController(_ scannerController: DataScannerViewController, context: Context) {}
|
|
322
253
|
|
|
323
|
-
func makeCoordinator() ->
|
|
324
|
-
|
|
254
|
+
func makeCoordinator() -> Tapper {
|
|
255
|
+
Tapper(owner: self)
|
|
325
256
|
}
|
|
326
257
|
|
|
327
|
-
static func dismantleUIViewController(
|
|
328
|
-
|
|
329
|
-
coordinator: Coordinator
|
|
330
|
-
) {
|
|
331
|
-
controller.stopScanning()
|
|
258
|
+
static func dismantleUIViewController(_ scannerController: DataScannerViewController, coordinator: Tapper) {
|
|
259
|
+
scannerController.stopScanning()
|
|
332
260
|
}
|
|
333
261
|
|
|
334
262
|
@MainActor
|
|
335
|
-
final class
|
|
336
|
-
let
|
|
263
|
+
final class Tapper: NSObject, DataScannerViewControllerDelegate {
|
|
264
|
+
private let owner: LiveScanner
|
|
337
265
|
|
|
338
|
-
init(
|
|
339
|
-
self.
|
|
266
|
+
init(owner: LiveScanner) {
|
|
267
|
+
self.owner = owner
|
|
340
268
|
}
|
|
341
269
|
|
|
342
|
-
func dataScanner(
|
|
343
|
-
_ scanner: DataScannerViewController,
|
|
344
|
-
didTapOn item: RecognizedItem
|
|
345
|
-
) {
|
|
270
|
+
func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) {
|
|
346
271
|
switch item {
|
|
347
|
-
case .text(let
|
|
348
|
-
|
|
349
|
-
case .barcode(let
|
|
350
|
-
if let
|
|
351
|
-
|
|
272
|
+
case .text(let recognized):
|
|
273
|
+
owner.lines.append(recognized.transcript)
|
|
274
|
+
case .barcode(let code):
|
|
275
|
+
if let value = code.payloadStringValue {
|
|
276
|
+
owner.payloads.append(value)
|
|
352
277
|
}
|
|
353
278
|
@unknown default:
|
|
354
279
|
break
|
|
355
280
|
}
|
|
356
281
|
}
|
|
357
|
-
|
|
358
|
-
func dataScanner(
|
|
359
|
-
_ scanner: DataScannerViewController,
|
|
360
|
-
didAdd addedItems: [RecognizedItem],
|
|
361
|
-
allItems: [RecognizedItem]
|
|
362
|
-
) {
|
|
363
|
-
// Handle newly recognized items
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
func dataScanner(
|
|
367
|
-
_ scanner: DataScannerViewController,
|
|
368
|
-
didUpdate updatedItems: [RecognizedItem],
|
|
369
|
-
allItems: [RecognizedItem]
|
|
370
|
-
) {
|
|
371
|
-
// Handle item updates
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
func dataScanner(
|
|
375
|
-
_ scanner: DataScannerViewController,
|
|
376
|
-
didRemove removedItems: [RecognizedItem],
|
|
377
|
-
allItems: [RecognizedItem]
|
|
378
|
-
) {
|
|
379
|
-
// Handle removed items
|
|
380
|
-
}
|
|
381
282
|
}
|
|
382
283
|
}
|
|
383
284
|
```
|
|
384
285
|
|
|
385
|
-
|
|
286
|
+
`startScanning()` must wait until the controller is presented. The deferred
|
|
287
|
+
`Task` above runs after `makeUIViewController` returns; stopping belongs in
|
|
288
|
+
`dismantleUIViewController`.
|
|
289
|
+
|
|
290
|
+
A screen that offers the scanner only where it can run:
|
|
386
291
|
|
|
387
292
|
```swift
|
|
388
|
-
import SwiftUI
|
|
389
293
|
import VisionKit
|
|
294
|
+
import SwiftUI
|
|
390
295
|
|
|
391
|
-
struct
|
|
392
|
-
@State private var
|
|
393
|
-
@State private var
|
|
394
|
-
@State private var
|
|
296
|
+
struct ShelfScanScreen: View {
|
|
297
|
+
@State private var scanning = false
|
|
298
|
+
@State private var lines: [String] = []
|
|
299
|
+
@State private var payloads: [String] = []
|
|
395
300
|
|
|
396
301
|
var body: some View {
|
|
397
|
-
|
|
302
|
+
Form {
|
|
398
303
|
if DataScannerViewController.isSupported {
|
|
399
|
-
Button("Scan") {
|
|
400
|
-
isShowingScanner = true
|
|
401
|
-
}
|
|
402
|
-
.fullScreenCover(isPresented: $isShowingScanner) {
|
|
403
|
-
NavigationStack {
|
|
404
|
-
DataScannerRepresentable(
|
|
405
|
-
recognizedDataTypes: [
|
|
406
|
-
.text(languages: ["en"]),
|
|
407
|
-
.barcode(symbologies: [.qr]),
|
|
408
|
-
],
|
|
409
|
-
qualityLevel: .balanced,
|
|
410
|
-
recognizesMultipleItems: true,
|
|
411
|
-
recognizedText: $recognizedText,
|
|
412
|
-
recognizedBarcodes: $recognizedBarcodes
|
|
413
|
-
)
|
|
414
|
-
.ignoresSafeArea()
|
|
415
|
-
.toolbar {
|
|
416
|
-
ToolbarItem(placement: .cancellationAction) {
|
|
417
|
-
Button("Done") {
|
|
418
|
-
isShowingScanner = false
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
304
|
+
Button("Scan labels") { scanning = true }
|
|
424
305
|
} else {
|
|
425
|
-
ContentUnavailableView(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
description: Text("This device does not support scanning.")
|
|
429
|
-
)
|
|
306
|
+
ContentUnavailableView("Scanning unavailable",
|
|
307
|
+
systemImage: "camera.fill",
|
|
308
|
+
description: Text("Live scanning needs a newer iPhone or iPad."))
|
|
430
309
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
310
|
+
Section("Text") { ForEach(lines, id: \.self, content: Text.init) }
|
|
311
|
+
Section("Codes") { ForEach(payloads, id: \.self, content: Text.init) }
|
|
312
|
+
}
|
|
313
|
+
.fullScreenCover(isPresented: $scanning) {
|
|
314
|
+
NavigationStack {
|
|
315
|
+
LiveScanner(kinds: [.text(), .barcode()], lines: $lines, payloads: $payloads)
|
|
316
|
+
.ignoresSafeArea()
|
|
317
|
+
.toolbar {
|
|
318
|
+
ToolbarItem(placement: .cancellationAction) {
|
|
319
|
+
Button("Close") { scanning = false }
|
|
320
|
+
}
|
|
441
321
|
}
|
|
442
|
-
}
|
|
443
322
|
}
|
|
444
323
|
}
|
|
445
324
|
}
|
|
446
325
|
}
|
|
447
326
|
```
|
|
448
327
|
|
|
449
|
-
|
|
328
|
+
## Custom overlay
|
|
450
329
|
|
|
451
|
-
|
|
452
|
-
Use `onAppear` with a coordinator flag or start in the completion handler:
|
|
330
|
+
Layer your own guidance on top of the scanner with a `ZStack`:
|
|
453
331
|
|
|
454
332
|
```swift
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
let scanner = DataScannerViewController(
|
|
458
|
-
recognizedDataTypes: [.text(languages: ["en"])],
|
|
459
|
-
qualityLevel: .balanced,
|
|
460
|
-
recognizesMultipleItems: false,
|
|
461
|
-
isHighFrameRateTrackingEnabled: true,
|
|
462
|
-
isHighlightingEnabled: true
|
|
463
|
-
)
|
|
464
|
-
scanner.delegate = context.coordinator
|
|
465
|
-
// Start scanning after a brief delay to ensure presentation is complete
|
|
466
|
-
Task { @MainActor in
|
|
467
|
-
try? scanner.startScanning()
|
|
468
|
-
}
|
|
469
|
-
return scanner
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
func updateUIViewController(
|
|
473
|
-
_ controller: DataScannerViewController,
|
|
474
|
-
context: Context
|
|
475
|
-
) {}
|
|
476
|
-
|
|
477
|
-
func makeCoordinator() -> ScannerCoordinator {
|
|
478
|
-
ScannerCoordinator()
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
static func dismantleUIViewController(
|
|
482
|
-
_ controller: DataScannerViewController,
|
|
483
|
-
coordinator: ScannerCoordinator
|
|
484
|
-
) {
|
|
485
|
-
controller.stopScanning()
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
## Custom Overlay UI
|
|
491
|
-
|
|
492
|
-
Add custom views on top of the scanner for region-of-interest indicators,
|
|
493
|
-
instructions, or result display.
|
|
494
|
-
|
|
495
|
-
### Overlay with Region of Interest
|
|
333
|
+
import VisionKit
|
|
334
|
+
import SwiftUI
|
|
496
335
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
@State private var
|
|
500
|
-
@State private var lastScannedText = ""
|
|
336
|
+
struct FramedScanner: View {
|
|
337
|
+
@State private var lines: [String] = []
|
|
338
|
+
@State private var payloads: [String] = []
|
|
501
339
|
|
|
502
340
|
var body: some View {
|
|
503
341
|
ZStack {
|
|
504
|
-
|
|
342
|
+
LiveScanner(kinds: [.barcode()], lines: $lines, payloads: $payloads)
|
|
505
343
|
.ignoresSafeArea()
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
.font(.subheadline)
|
|
511
|
-
.padding(.horizontal)
|
|
512
|
-
.padding(.vertical)
|
|
344
|
+
VStack(spacing: 24) {
|
|
345
|
+
Text("Fit the code inside the frame")
|
|
346
|
+
.padding(.horizontal, 16)
|
|
347
|
+
.padding(.vertical, 8)
|
|
513
348
|
.background(.ultraThinMaterial, in: Capsule())
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
RoundedRectangle(cornerRadius: 12)
|
|
520
|
-
.strokeBorder(.white.opacity(0.6), lineWidth: 2)
|
|
521
|
-
.frame(width: 280, height: 180)
|
|
522
|
-
|
|
523
|
-
Spacer()
|
|
524
|
-
|
|
525
|
-
// Result display
|
|
526
|
-
if !lastScannedText.isEmpty {
|
|
527
|
-
Text(lastScannedText)
|
|
528
|
-
.font(.body)
|
|
529
|
-
.padding()
|
|
530
|
-
.frame(maxWidth: .infinity)
|
|
531
|
-
.background(.ultraThinMaterial)
|
|
532
|
-
.clipShape(.rect(cornerRadius: 12))
|
|
349
|
+
RoundedRectangle(cornerRadius: 16)
|
|
350
|
+
.stroke(.white.opacity(0.7), lineWidth: 3)
|
|
351
|
+
.frame(width: 260, height: 260)
|
|
352
|
+
if let latest = payloads.last {
|
|
353
|
+
Text(latest)
|
|
533
354
|
.padding()
|
|
355
|
+
.background(.thinMaterial, in: .rect(cornerRadius: 12))
|
|
534
356
|
}
|
|
535
357
|
}
|
|
536
358
|
}
|
|
@@ -538,201 +360,203 @@ struct ScannerWithOverlay: View {
|
|
|
538
360
|
}
|
|
539
361
|
```
|
|
540
362
|
|
|
363
|
+
Sizes in these examples are placeholders; take them from the design system.
|
|
364
|
+
|
|
541
365
|
## VNDocumentCameraViewController
|
|
542
366
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
all devices running iOS 13+.
|
|
367
|
+
Presents a camera that fills the screen, snaps pages on its own, straightens
|
|
368
|
+
them, and collects several pages per session. It works on every device that runs iOS 13 or later.
|
|
546
369
|
|
|
547
|
-
|
|
370
|
+
A small helper used by both versions below:
|
|
548
371
|
|
|
549
372
|
```swift
|
|
550
373
|
import VisionKit
|
|
551
374
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
375
|
+
extension VNDocumentCameraScan {
|
|
376
|
+
var allPages: [UIImage] {
|
|
377
|
+
var collected: [UIImage] = []
|
|
378
|
+
for index in 0..<pageCount {
|
|
379
|
+
collected.append(imageOfPage(at: index))
|
|
380
|
+
}
|
|
381
|
+
return collected
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
UIKit:
|
|
387
|
+
|
|
388
|
+
```swift
|
|
389
|
+
import UIKit
|
|
390
|
+
import VisionKit
|
|
556
391
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
392
|
+
@MainActor
|
|
393
|
+
final class PaperScanFlow: NSObject, @MainActor VNDocumentCameraViewControllerDelegate {
|
|
394
|
+
private weak var host: UIViewController?
|
|
395
|
+
var onPages: ([UIImage]) -> Void = { _ in }
|
|
396
|
+
var onFailure: (Error) -> Void = { _ in }
|
|
397
|
+
|
|
398
|
+
func start(from host: UIViewController) {
|
|
399
|
+
self.host = host
|
|
400
|
+
let camera = VNDocumentCameraViewController()
|
|
401
|
+
camera.delegate = self
|
|
402
|
+
host.present(camera, animated: true)
|
|
561
403
|
}
|
|
562
404
|
|
|
563
|
-
func documentCameraViewController(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
controller.dismiss(animated: true)
|
|
568
|
-
for pageIndex in 0..<scan.pageCount {
|
|
569
|
-
let pageImage = scan.imageOfPage(at: pageIndex)
|
|
570
|
-
// Process each scanned page image
|
|
571
|
-
}
|
|
405
|
+
func documentCameraViewController(_ camera: VNDocumentCameraViewController,
|
|
406
|
+
didFinishWith scan: VNDocumentCameraScan) {
|
|
407
|
+
camera.dismiss(animated: true)
|
|
408
|
+
onPages(scan.allPages)
|
|
572
409
|
}
|
|
573
410
|
|
|
574
|
-
func documentCameraViewControllerDidCancel(
|
|
575
|
-
|
|
576
|
-
) {
|
|
577
|
-
controller.dismiss(animated: true)
|
|
411
|
+
func documentCameraViewControllerDidCancel(_ camera: VNDocumentCameraViewController) {
|
|
412
|
+
camera.dismiss(animated: true)
|
|
578
413
|
}
|
|
579
414
|
|
|
580
|
-
func documentCameraViewController(
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
controller.dismiss(animated: true)
|
|
585
|
-
// Handle scanning error
|
|
415
|
+
func documentCameraViewController(_ camera: VNDocumentCameraViewController,
|
|
416
|
+
didFailWithError error: Error) {
|
|
417
|
+
camera.dismiss(animated: true)
|
|
418
|
+
onFailure(error)
|
|
586
419
|
}
|
|
587
420
|
}
|
|
588
421
|
```
|
|
589
422
|
|
|
590
|
-
|
|
423
|
+
`VNDocumentCameraViewControllerDelegate` carries no actor annotation, so the
|
|
424
|
+
conformance is written as `@MainActor VNDocumentCameraViewControllerDelegate`
|
|
425
|
+
(an isolated conformance, Swift 6.2 and later). The presenter is held `weak`
|
|
426
|
+
so the flow object never keeps a dismissed screen alive.
|
|
427
|
+
|
|
428
|
+
SwiftUI:
|
|
591
429
|
|
|
592
430
|
```swift
|
|
593
|
-
import SwiftUI
|
|
594
431
|
import VisionKit
|
|
432
|
+
import SwiftUI
|
|
595
433
|
|
|
596
|
-
struct
|
|
597
|
-
@Binding var
|
|
598
|
-
@Environment(\.dismiss) private var
|
|
434
|
+
struct PaperCamera: UIViewControllerRepresentable {
|
|
435
|
+
@Binding var pages: [UIImage]
|
|
436
|
+
@Environment(\.dismiss) private var close
|
|
599
437
|
|
|
600
438
|
func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
|
|
601
|
-
let
|
|
602
|
-
|
|
603
|
-
return
|
|
439
|
+
let camera = VNDocumentCameraViewController()
|
|
440
|
+
camera.delegate = context.coordinator
|
|
441
|
+
return camera
|
|
604
442
|
}
|
|
605
443
|
|
|
606
|
-
func updateUIViewController(
|
|
607
|
-
_ controller: VNDocumentCameraViewController,
|
|
608
|
-
context: Context
|
|
609
|
-
) {}
|
|
444
|
+
func updateUIViewController(_ camera: VNDocumentCameraViewController, context: Context) {}
|
|
610
445
|
|
|
611
|
-
func makeCoordinator() ->
|
|
612
|
-
|
|
446
|
+
func makeCoordinator() -> Receiver {
|
|
447
|
+
Receiver(owner: self)
|
|
613
448
|
}
|
|
614
449
|
|
|
615
450
|
@MainActor
|
|
616
|
-
final class
|
|
617
|
-
let
|
|
451
|
+
final class Receiver: NSObject, @MainActor VNDocumentCameraViewControllerDelegate {
|
|
452
|
+
private let owner: PaperCamera
|
|
618
453
|
|
|
619
|
-
init(
|
|
620
|
-
self.
|
|
454
|
+
init(owner: PaperCamera) {
|
|
455
|
+
self.owner = owner
|
|
621
456
|
}
|
|
622
457
|
|
|
623
|
-
func documentCameraViewController(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
parent.scannedImages = (0..<scan.pageCount).map { scan.imageOfPage(at: $0) }
|
|
628
|
-
parent.dismiss()
|
|
458
|
+
func documentCameraViewController(_ camera: VNDocumentCameraViewController,
|
|
459
|
+
didFinishWith scan: VNDocumentCameraScan) {
|
|
460
|
+
owner.pages = scan.allPages
|
|
461
|
+
owner.close()
|
|
629
462
|
}
|
|
630
463
|
|
|
631
|
-
func documentCameraViewControllerDidCancel(
|
|
632
|
-
|
|
633
|
-
) {
|
|
634
|
-
parent.dismiss()
|
|
464
|
+
func documentCameraViewControllerDidCancel(_ camera: VNDocumentCameraViewController) {
|
|
465
|
+
owner.close()
|
|
635
466
|
}
|
|
636
467
|
|
|
637
|
-
func documentCameraViewController(
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
) {
|
|
641
|
-
parent.dismiss()
|
|
468
|
+
func documentCameraViewController(_ camera: VNDocumentCameraViewController,
|
|
469
|
+
didFailWithError error: Error) {
|
|
470
|
+
owner.close()
|
|
642
471
|
}
|
|
643
472
|
}
|
|
644
473
|
}
|
|
645
474
|
```
|
|
646
475
|
|
|
647
|
-
###
|
|
476
|
+
### Scan then read
|
|
648
477
|
|
|
649
|
-
|
|
478
|
+
A model that runs OCR over every captured page, and a view that drives it:
|
|
650
479
|
|
|
651
480
|
```swift
|
|
481
|
+
import Observation
|
|
652
482
|
import SwiftUI
|
|
653
|
-
import VisionKit
|
|
654
483
|
import Vision
|
|
484
|
+
import VisionKit
|
|
655
485
|
|
|
656
486
|
@MainActor
|
|
657
487
|
@Observable
|
|
658
|
-
final class
|
|
659
|
-
var
|
|
660
|
-
var
|
|
661
|
-
var
|
|
662
|
-
|
|
663
|
-
func
|
|
664
|
-
|
|
665
|
-
defer {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
488
|
+
final class PaperReader {
|
|
489
|
+
var pages: [UIImage] = []
|
|
490
|
+
var transcripts: [String] = []
|
|
491
|
+
var busy = false
|
|
492
|
+
|
|
493
|
+
func transcribe() async {
|
|
494
|
+
busy = true
|
|
495
|
+
defer { busy = false }
|
|
496
|
+
var results: [String] = []
|
|
497
|
+
for page in pages {
|
|
498
|
+
guard let bitmap = page.cgImage else {
|
|
499
|
+
results.append("(page could not be read)")
|
|
500
|
+
continue
|
|
501
|
+
}
|
|
502
|
+
var request = RecognizeTextRequest()
|
|
503
|
+
request.usesLanguageCorrection = true
|
|
504
|
+
request.recognitionLanguages = [Locale.Language(identifier: "en-GB")]
|
|
505
|
+
request.recognitionLevel = .accurate
|
|
670
506
|
do {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
request.usesLanguageCorrection = true
|
|
675
|
-
|
|
676
|
-
let observations = try await request.perform(on: cgImage)
|
|
677
|
-
let pageText = observations
|
|
678
|
-
.compactMap { $0.topCandidates(1).first?.string }
|
|
679
|
-
.joined(separator: "\n")
|
|
680
|
-
extractedText.append(pageText)
|
|
507
|
+
let found = try await request.perform(on: bitmap)
|
|
508
|
+
let body = found.compactMap { $0.topCandidates(1).first }.map(\.string)
|
|
509
|
+
results.append(body.joined(separator: "\n"))
|
|
681
510
|
} catch {
|
|
682
|
-
|
|
511
|
+
results.append("(recognition failed)")
|
|
683
512
|
}
|
|
684
513
|
}
|
|
514
|
+
transcripts = results
|
|
685
515
|
}
|
|
686
516
|
}
|
|
687
517
|
|
|
688
|
-
struct
|
|
689
|
-
@State private var
|
|
690
|
-
@State private var
|
|
518
|
+
struct PaperReaderScreen: View {
|
|
519
|
+
@State private var reader = PaperReader()
|
|
520
|
+
@State private var capturing = false
|
|
691
521
|
|
|
692
522
|
var body: some View {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
ForEach(Array(model.extractedText.enumerated()), id: \.offset) { index, text in
|
|
699
|
-
Section("Page \(index + 1)") {
|
|
700
|
-
Text(text)
|
|
701
|
-
.font(.body)
|
|
702
|
-
.textSelection(.enabled)
|
|
703
|
-
}
|
|
704
|
-
}
|
|
523
|
+
Form {
|
|
524
|
+
Button("Scan pages") { capturing = true }
|
|
525
|
+
if reader.busy {
|
|
526
|
+
ProgressView()
|
|
705
527
|
}
|
|
706
|
-
.
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
isShowingScanner = true
|
|
528
|
+
ForEach(reader.transcripts.indices, id: \.self) { page in
|
|
529
|
+
Section("Page \(page + 1)") {
|
|
530
|
+
Text(reader.transcripts[page]).textSelection(.enabled)
|
|
710
531
|
}
|
|
711
532
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
533
|
+
}
|
|
534
|
+
.sheet(isPresented: $capturing) {
|
|
535
|
+
PaperCamera(pages: $reader.pages)
|
|
536
|
+
}
|
|
537
|
+
.onChange(of: reader.pages) {
|
|
538
|
+
Task { await reader.transcribe() }
|
|
718
539
|
}
|
|
719
540
|
}
|
|
720
541
|
}
|
|
721
542
|
```
|
|
722
543
|
|
|
723
|
-
|
|
544
|
+
User-facing strings here are literals for brevity; ship them as localized keys.
|
|
545
|
+
|
|
546
|
+
## Performance
|
|
724
547
|
|
|
725
|
-
|
|
548
|
+
Live scanner:
|
|
726
549
|
|
|
727
|
-
- Use `.fast`
|
|
728
|
-
- Set `recognizesMultipleItems = false` when
|
|
729
|
-
-
|
|
730
|
-
|
|
731
|
-
-
|
|
550
|
+
- Use `.fast` when only barcodes are scanned.
|
|
551
|
+
- Set `recognizesMultipleItems = false` when one result is enough.
|
|
552
|
+
- Turn off `isHighFrameRateTrackingEnabled` for barcode-only scanners to save
|
|
553
|
+
power.
|
|
554
|
+
- Ask only for the `recognizedDataTypes` the feature needs.
|
|
555
|
+
- Call `stopScanning()` while you process a result, and resume afterwards.
|
|
732
556
|
|
|
733
|
-
|
|
557
|
+
Document camera:
|
|
734
558
|
|
|
735
|
-
- Pages
|
|
736
|
-
|
|
737
|
-
- Process pages
|
|
738
|
-
-
|
|
559
|
+
- Pages arrive as full-resolution `UIImage` values; downscale them if memory
|
|
560
|
+
is tight.
|
|
561
|
+
- Process pages one after another rather than all at once, and wrap
|
|
562
|
+
synchronous per-page loops in `autoreleasepool` when there are many.
|