@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,47 +1,82 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Core NFC Patterns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Longer examples for the `core-nfc` skill. Each snippet assumes the session is
|
|
4
|
+
already running and the tag is connected, as shown in the parent SKILL.md. Tag
|
|
5
|
+
calls use their `async` forms from inside a `Task`, which avoids the Swift 6
|
|
6
|
+
warnings about non-`Sendable` sessions and tags captured in `@Sendable`
|
|
7
|
+
completion handlers. A delegate callback that starts such a `Task` passes the
|
|
8
|
+
session and tag in as `nonisolated(unsafe)` copies; a plain capture fails the
|
|
9
|
+
region-isolation check.
|
|
4
10
|
|
|
5
11
|
## Contents
|
|
6
12
|
|
|
7
|
-
- [
|
|
8
|
-
- [ISO
|
|
9
|
-
- [
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
13
|
+
- [Handling several tags at once](#handling-several-tags-at-once)
|
|
14
|
+
- [ISO 7816 APDU commands](#iso-7816-apdu-commands)
|
|
15
|
+
- [ISO 15693 tags](#iso-15693-tags)
|
|
16
|
+
- [MIFARE tags](#mifare-tags)
|
|
17
|
+
- [FeliCa tags](#felica-tags)
|
|
18
|
+
- [Messages with several records](#messages-with-several-records)
|
|
19
|
+
- [Locking a tag](#locking-a-tag)
|
|
20
|
+
- [A SwiftUI scanner](#a-swiftui-scanner)
|
|
21
|
+
- [Error codes](#error-codes)
|
|
15
22
|
|
|
16
|
-
##
|
|
23
|
+
## Handling several tags at once
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
`tagReaderSession(_:didDetect:)` and `readerSession(_:didDetect:)` hand over an
|
|
26
|
+
array. More than one entry means several tags are in the field together, and
|
|
27
|
+
commands sent to one of them may be answered by the wrong chip or not at all.
|
|
19
28
|
|
|
20
29
|
```swift
|
|
21
|
-
|
|
30
|
+
func tagReaderSession(_ reader: NFCTagReaderSession, didDetect found: [NFCTag]) {
|
|
31
|
+
nonisolated(unsafe) let reader = reader
|
|
32
|
+
if found.count > 1 {
|
|
33
|
+
reader.alertMessage = "Several tags are in range. Keep only one near the phone."
|
|
34
|
+
Task {
|
|
35
|
+
try? await Task.sleep(for: .milliseconds(500))
|
|
36
|
+
reader.restartPolling()
|
|
37
|
+
}
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
if !found.isEmpty {
|
|
41
|
+
nonisolated(unsafe) let tags = found
|
|
42
|
+
Task {
|
|
43
|
+
try? await reader.connect(to: tags[0])
|
|
44
|
+
// Continue with the single tag.
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
reader.restartPolling()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The session stays alive while polling restarts, so the user does not see the
|
|
53
|
+
sheet close and reopen. Scanning a series of tags one after another follows the
|
|
54
|
+
same idea: after finishing with a tag, update `alertMessage` and call
|
|
55
|
+
`restartPolling()` instead of invalidating.
|
|
56
|
+
|
|
57
|
+
## ISO 7816 APDU commands
|
|
22
58
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let
|
|
59
|
+
An APDU is built from class, instruction, two parameters, data and the expected
|
|
60
|
+
response length. Selecting an application by its identifier:
|
|
61
|
+
|
|
62
|
+
```swift
|
|
63
|
+
func select(_ card: any NFCISO7816Tag, in session: NFCTagReaderSession) async {
|
|
64
|
+
let aid = Data([0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
|
|
65
|
+
let selectApp = NFCISO7816APDU(
|
|
29
66
|
instructionClass: 0x00,
|
|
30
67
|
instructionCode: 0xA4,
|
|
31
68
|
p1Parameter: 0x04,
|
|
32
69
|
p2Parameter: 0x00,
|
|
33
|
-
data:
|
|
70
|
+
data: aid,
|
|
34
71
|
expectedResponseLength: -1
|
|
35
72
|
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
guard
|
|
39
|
-
session.invalidate(errorMessage: "
|
|
73
|
+
do {
|
|
74
|
+
let (_, sw1, sw2) = try await card.sendCommand(apdu: selectApp)
|
|
75
|
+
guard (sw1, sw2) == (0x90, 0x00) else {
|
|
76
|
+
session.invalidate(errorMessage: "The card rejected the application.")
|
|
40
77
|
return
|
|
41
78
|
}
|
|
42
|
-
|
|
43
|
-
// Read binary
|
|
44
|
-
let readBinary = NFCISO7816APDU(
|
|
79
|
+
let readFile = NFCISO7816APDU(
|
|
45
80
|
instructionClass: 0x00,
|
|
46
81
|
instructionCode: 0xB0,
|
|
47
82
|
p1Parameter: 0x00,
|
|
@@ -49,402 +84,271 @@ func readISO7816(
|
|
|
49
84
|
data: Data(),
|
|
50
85
|
expectedResponseLength: 256
|
|
51
86
|
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
print("Read \(data.count) bytes, SW: \(sw1) \(sw2)")
|
|
59
|
-
session.alertMessage = "Tag read successfully."
|
|
60
|
-
session.invalidate()
|
|
61
|
-
}
|
|
87
|
+
let (body, status1, status2) = try await card.sendCommand(apdu: readFile)
|
|
88
|
+
let ok = status1 == 0x90 && status2 == 0x00
|
|
89
|
+
session.alertMessage = ok ? "Read \(body.count) bytes." : "Read failed."
|
|
90
|
+
session.invalidate()
|
|
91
|
+
} catch {
|
|
92
|
+
session.invalidate(errorMessage: "The card stopped answering.")
|
|
62
93
|
}
|
|
63
94
|
}
|
|
64
95
|
```
|
|
65
96
|
|
|
66
|
-
|
|
97
|
+
`90 00` is the success status word. An `expectedResponseLength` of `-1` lets the
|
|
98
|
+
card decide how much to return. The AID used here must also appear in the
|
|
99
|
+
Info.plist select-identifiers list.
|
|
67
100
|
|
|
68
|
-
| Command | CLA | INS |
|
|
69
|
-
|
|
70
|
-
| SELECT | 0x00 | 0xA4 |
|
|
71
|
-
| READ BINARY | 0x00 | 0xB0 |
|
|
72
|
-
| UPDATE BINARY | 0x00 | 0xD6 |
|
|
73
|
-
| READ RECORD | 0x00 | 0xB2 |
|
|
74
|
-
| GET DATA | 0x00 | 0xCA |
|
|
101
|
+
| Command | CLA | INS |
|
|
102
|
+
|---------|-----|-----|
|
|
103
|
+
| SELECT | 0x00 | 0xA4 |
|
|
104
|
+
| READ BINARY | 0x00 | 0xB0 |
|
|
105
|
+
| UPDATE BINARY | 0x00 | 0xD6 |
|
|
106
|
+
| READ RECORD | 0x00 | 0xB2 |
|
|
107
|
+
| GET DATA | 0x00 | 0xCA |
|
|
75
108
|
|
|
76
|
-
|
|
109
|
+
A small helper used by the examples below:
|
|
77
110
|
|
|
78
111
|
```swift
|
|
79
|
-
func
|
|
80
|
-
|
|
81
|
-
session: NFCTagReaderSession
|
|
82
|
-
) {
|
|
83
|
-
// Read a single block
|
|
84
|
-
tag.readSingleBlock(
|
|
85
|
-
requestFlags: [.highDataRate, .address],
|
|
86
|
-
blockNumber: 0
|
|
87
|
-
) { data, error in
|
|
88
|
-
guard let data, error == nil else {
|
|
89
|
-
session.invalidate(errorMessage: "Read failed.")
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
print("Block 0: \(data.map { String(format: "%02x", $0) }.joined())")
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Read multiple blocks
|
|
96
|
-
tag.readMultipleBlocks(
|
|
97
|
-
requestFlags: [.highDataRate, .address],
|
|
98
|
-
blockRange: NSRange(location: 0, length: 4)
|
|
99
|
-
) { blocks, error in
|
|
100
|
-
guard let blocks, error == nil else { return }
|
|
101
|
-
for (index, block) in blocks.enumerated() {
|
|
102
|
-
print("Block \(index): \(block.count) bytes")
|
|
103
|
-
}
|
|
104
|
-
session.alertMessage = "Read \(blocks.count) blocks."
|
|
105
|
-
session.invalidate()
|
|
106
|
-
}
|
|
112
|
+
func hex(_ bytes: Data) -> String {
|
|
113
|
+
bytes.map { String(format: "%02X", $0) }.joined()
|
|
107
114
|
}
|
|
108
115
|
```
|
|
109
116
|
|
|
110
|
-
|
|
117
|
+
## ISO 15693 tags
|
|
111
118
|
|
|
112
119
|
```swift
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
func readBlocks(_ label: any NFCISO15693Tag, in session: NFCTagReaderSession) async {
|
|
121
|
+
do {
|
|
122
|
+
let info = try await label.systemInfo(requestFlags: [.highDataRate])
|
|
123
|
+
let uidHex = hex(info.uniqueIdentifier)
|
|
124
|
+
session.alertMessage = "Tag \(uidHex): \(info.totalBlocks) blocks of \(info.blockSize) bytes"
|
|
125
|
+
|
|
126
|
+
let first = try await label.readSingleBlock(
|
|
127
|
+
requestFlags: [.address, .highDataRate],
|
|
128
|
+
blockNumber: 0
|
|
129
|
+
)
|
|
130
|
+
let rest = try await label.readMultipleBlocks(
|
|
131
|
+
requestFlags: [.address, .highDataRate],
|
|
132
|
+
blockRange: NSRange(location: 1, length: 4)
|
|
133
|
+
)
|
|
134
|
+
session.alertMessage = "Read \(1 + rest.count) blocks, \(first.count) bytes in block 0."
|
|
135
|
+
session.invalidate()
|
|
136
|
+
} catch {
|
|
137
|
+
session.invalidate(errorMessage: "No system info.")
|
|
138
|
+
}
|
|
118
139
|
}
|
|
119
140
|
```
|
|
120
141
|
|
|
121
|
-
|
|
142
|
+
`systemInfo(requestFlags:)` returns an `NFCISO15693SystemInfo`: the
|
|
143
|
+
identifier, DSFID, AFI, block size, total block count and IC reference, which
|
|
144
|
+
tells you how far you can read. The callback form, `getSystemInfo`, hands the
|
|
145
|
+
same value over in a `Result`.
|
|
146
|
+
|
|
147
|
+
## MIFARE tags
|
|
122
148
|
|
|
123
149
|
```swift
|
|
124
|
-
func
|
|
125
|
-
|
|
126
|
-
session: NFCTagReaderSession
|
|
127
|
-
) {
|
|
128
|
-
// Identify MIFARE family
|
|
129
|
-
switch tag.mifareFamily {
|
|
150
|
+
func inspect(_ chip: any NFCMiFareTag, in session: NFCTagReaderSession) async {
|
|
151
|
+
switch chip.mifareFamily {
|
|
130
152
|
case .ultralight:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
session.invalidate(
|
|
134
|
-
errorMessage: "Use app-specific DESFire commands for this card."
|
|
135
|
-
)
|
|
136
|
-
case .plus:
|
|
137
|
-
print("MIFARE Plus tag detected")
|
|
138
|
-
session.invalidate()
|
|
139
|
-
case .unknown:
|
|
140
|
-
print("Unknown MIFARE tag")
|
|
153
|
+
let pages = try? await chip.sendMiFareCommand(commandPacket: Data([0x30, 0x04]))
|
|
154
|
+
session.alertMessage = pages == nil ? "Read failed." : "Pages 4 to 7 read."
|
|
141
155
|
session.invalidate()
|
|
156
|
+
case .desfire:
|
|
157
|
+
// DESFire needs the application-specific command set of your card issuer.
|
|
158
|
+
session.invalidate(errorMessage: "DESFire cards need a dedicated command set.")
|
|
159
|
+
case .plus, .unknown:
|
|
160
|
+
session.invalidate(errorMessage: "This MIFARE type is not handled.")
|
|
142
161
|
@unknown default:
|
|
143
|
-
session.invalidate()
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
func readMiFareUltralight(
|
|
148
|
-
tag: NFCMiFareTag,
|
|
149
|
-
session: NFCTagReaderSession
|
|
150
|
-
) {
|
|
151
|
-
// READ command: reads 4 pages starting at page 4
|
|
152
|
-
let readCommand = Data([0x30, 0x04])
|
|
153
|
-
|
|
154
|
-
tag.sendMiFareCommand(commandPacket: readCommand) { data, error in
|
|
155
|
-
guard error == nil else {
|
|
156
|
-
session.invalidate(errorMessage: "Read failed.")
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
print("Read \(data.count) bytes from MIFARE Ultralight")
|
|
160
|
-
session.alertMessage = "Tag read successfully."
|
|
161
|
-
session.invalidate()
|
|
162
|
+
session.invalidate(errorMessage: "Unrecognised MIFARE family.")
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
```
|
|
165
166
|
|
|
166
|
-
|
|
167
|
+
The Ultralight READ command `0x30` with page `0x04` returns four pages
|
|
168
|
+
starting at page 4.
|
|
167
169
|
|
|
168
|
-
FeliCa
|
|
169
|
-
`com.apple.developer.nfc.readersession.felica.systemcodes` entries in
|
|
170
|
-
Info.plist. Each system code must be explicit; wildcard values are not allowed.
|
|
170
|
+
## FeliCa tags
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
tag: NFCFeliCaTag,
|
|
175
|
-
session: NFCTagReaderSession
|
|
176
|
-
) {
|
|
177
|
-
tag.requestSystemCode { systemCodes, error in
|
|
178
|
-
guard error == nil else {
|
|
179
|
-
session.invalidate(errorMessage: "System code request failed.")
|
|
180
|
-
return
|
|
181
|
-
}
|
|
172
|
+
FeliCa cards are only found when the session polls `.iso18092` and the
|
|
173
|
+
Info.plist lists the exact system codes; wildcards are rejected.
|
|
182
174
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
session.invalidate()
|
|
189
|
-
}
|
|
175
|
+
```swift
|
|
176
|
+
func readSystemCodes(_ card: any NFCFeliCaTag, in session: NFCTagReaderSession) async {
|
|
177
|
+
let codes = (try? await card.requestSystemCode()) ?? []
|
|
178
|
+
session.alertMessage = "System codes: \(codes.map(hex).joined(separator: ", "))"
|
|
179
|
+
session.invalidate()
|
|
190
180
|
}
|
|
191
181
|
```
|
|
192
182
|
|
|
193
|
-
##
|
|
183
|
+
## Messages with several records
|
|
194
184
|
|
|
195
|
-
|
|
185
|
+
One NDEF message can mix record kinds, for example a label, a link and an app
|
|
186
|
+
payload:
|
|
196
187
|
|
|
197
188
|
```swift
|
|
198
|
-
func
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
) {
|
|
206
|
-
records.append(textPayload)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// URL record
|
|
210
|
-
if let urlPayload = NFCNDEFPayload.wellKnownTypeURIPayload(
|
|
211
|
-
url: URL(string: "https://example.com/product/123")!
|
|
212
|
-
) {
|
|
213
|
-
records.append(urlPayload)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Custom external type record
|
|
217
|
-
let externalPayload = NFCNDEFPayload(
|
|
189
|
+
func shelfMessage(for id: String) -> NFCNDEFMessage? {
|
|
190
|
+
guard
|
|
191
|
+
let label = NFCNDEFPayload.wellKnownTypeTextPayload(string: "Shelf \(id)", locale: Locale(identifier: "en")),
|
|
192
|
+
let address = URL(string: "https://example.com/shelf/" + id),
|
|
193
|
+
let link = NFCNDEFPayload.wellKnownTypeURIPayload(url: address)
|
|
194
|
+
else { return nil }
|
|
195
|
+
let data = NFCNDEFPayload(
|
|
218
196
|
format: .nfcExternal,
|
|
219
|
-
type: "com
|
|
197
|
+
type: Data("example.com:shelf".utf8),
|
|
220
198
|
identifier: Data(),
|
|
221
|
-
payload: """
|
|
222
|
-
{"sku":"WP-001","batch":"2026-03"}
|
|
223
|
-
""".data(using: .utf8)!
|
|
199
|
+
payload: Data(#"{"id":"\#(id)"}"#.utf8)
|
|
224
200
|
)
|
|
225
|
-
records
|
|
226
|
-
|
|
227
|
-
return NFCNDEFMessage(records: records)
|
|
201
|
+
return NFCNDEFMessage(records: [label, link, data])
|
|
228
202
|
}
|
|
229
203
|
```
|
|
230
204
|
|
|
231
|
-
### Checking
|
|
232
|
-
|
|
233
|
-
```swift
|
|
234
|
-
func writeIfFits(
|
|
235
|
-
message: NFCNDEFMessage,
|
|
236
|
-
to tag: any NFCNDEFTag,
|
|
237
|
-
session: NFCNDEFReaderSession
|
|
238
|
-
) {
|
|
239
|
-
tag.queryNDEFStatus { status, capacity, error in
|
|
240
|
-
guard status == .readWrite else {
|
|
241
|
-
session.invalidate(errorMessage: "Tag is not writable.")
|
|
242
|
-
return
|
|
243
|
-
}
|
|
205
|
+
### Checking capacity
|
|
244
206
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
session.invalidate(
|
|
248
|
-
errorMessage: "Message (\(messageLength) bytes) exceeds "
|
|
249
|
-
+ "tag capacity (\(capacity) bytes)."
|
|
250
|
-
)
|
|
251
|
-
return
|
|
252
|
-
}
|
|
207
|
+
`queryNDEFStatus` returns the capacity in bytes. Compare it with the message
|
|
208
|
+
length before writing:
|
|
253
209
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
}
|
|
210
|
+
```swift
|
|
211
|
+
let (status, capacity) = try await tag.queryNDEFStatus()
|
|
212
|
+
guard status == .readWrite else {
|
|
213
|
+
session.invalidate(errorMessage: "Writes are blocked on this tag.")
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
guard message.length <= capacity else {
|
|
217
|
+
session.invalidate(errorMessage: "Data needs \(message.length) bytes; the tag holds \(capacity).")
|
|
218
|
+
return
|
|
265
219
|
}
|
|
220
|
+
try await tag.writeNDEF(message)
|
|
221
|
+
session.invalidate()
|
|
266
222
|
```
|
|
267
223
|
|
|
268
|
-
##
|
|
224
|
+
## Locking a tag
|
|
269
225
|
|
|
270
|
-
|
|
226
|
+
`writeLock` turns a tag read-only for good. There is no undo, so confirm the
|
|
227
|
+
status is `.readWrite`, and confirm with the user, before calling it.
|
|
271
228
|
|
|
272
229
|
```swift
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
session
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
session.invalidate(errorMessage: "Tag is already read-only.")
|
|
280
|
-
return
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
tag.writeLock { error in
|
|
284
|
-
if let error {
|
|
285
|
-
session.invalidate(
|
|
286
|
-
errorMessage: "Lock failed: \(error.localizedDescription)"
|
|
287
|
-
)
|
|
288
|
-
} else {
|
|
289
|
-
session.alertMessage = "Tag locked permanently."
|
|
290
|
-
session.invalidate()
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
230
|
+
do {
|
|
231
|
+
try await tag.writeLock()
|
|
232
|
+
session.alertMessage = "Tag locked."
|
|
233
|
+
session.invalidate()
|
|
234
|
+
} catch {
|
|
235
|
+
session.invalidate(errorMessage: "Could not lock: \(error.localizedDescription)")
|
|
294
236
|
}
|
|
295
237
|
```
|
|
296
238
|
|
|
297
|
-
## SwiftUI
|
|
298
|
-
|
|
299
|
-
Wrap the NFC reader in an `@Observable` model for SwiftUI integration:
|
|
239
|
+
## A SwiftUI scanner
|
|
300
240
|
|
|
301
241
|
```swift
|
|
302
242
|
import CoreNFC
|
|
303
243
|
import SwiftUI
|
|
304
244
|
|
|
305
|
-
@Observable
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
var
|
|
309
|
-
var scannedURL: URL?
|
|
245
|
+
@Observable @MainActor
|
|
246
|
+
final class StickerScanner: NSObject {
|
|
247
|
+
var lastText: String?
|
|
248
|
+
var lastLink: URL?
|
|
310
249
|
var isScanning = false
|
|
311
|
-
var
|
|
250
|
+
var failure: String?
|
|
251
|
+
let isAvailable = NFCNDEFReaderSession.readingAvailable
|
|
312
252
|
|
|
313
253
|
private var session: NFCNDEFReaderSession?
|
|
314
254
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
func startScan() {
|
|
320
|
-
guard isAvailable else {
|
|
321
|
-
errorMessage = "NFC not available on this device."
|
|
322
|
-
return
|
|
323
|
-
}
|
|
324
|
-
|
|
255
|
+
func start() {
|
|
256
|
+
guard isAvailable else { return }
|
|
325
257
|
session = NFCNDEFReaderSession(
|
|
326
258
|
delegate: self,
|
|
327
259
|
queue: nil,
|
|
328
260
|
invalidateAfterFirstRead: true
|
|
329
261
|
)
|
|
330
|
-
session?.alertMessage = "Hold
|
|
262
|
+
session?.alertMessage = "Hold the sticker near your iPhone."
|
|
331
263
|
session?.begin()
|
|
332
264
|
isScanning = true
|
|
333
|
-
errorMessage = nil
|
|
334
265
|
}
|
|
335
266
|
}
|
|
336
267
|
|
|
337
|
-
extension
|
|
338
|
-
nonisolated func readerSessionDidBecomeActive(
|
|
339
|
-
_ session: NFCNDEFReaderSession
|
|
340
|
-
) { }
|
|
268
|
+
extension StickerScanner: NFCNDEFReaderSessionDelegate {
|
|
269
|
+
nonisolated func readerSessionDidBecomeActive(_ reader: NFCNDEFReaderSession) {}
|
|
341
270
|
|
|
342
|
-
nonisolated func readerSession(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
271
|
+
nonisolated func readerSession(_ reader: NFCNDEFReaderSession, didDetectNDEFs found: [NFCNDEFMessage]) {
|
|
272
|
+
let record = found.first?.records.first
|
|
273
|
+
let link = record?.wellKnownTypeURIPayload()
|
|
274
|
+
let text = record?.wellKnownTypeTextPayload().0
|
|
346
275
|
Task { @MainActor in
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if let url = record.wellKnownTypeURIPayload() {
|
|
350
|
-
scannedURL = url
|
|
351
|
-
}
|
|
352
|
-
if let (text, _) = record.wellKnownTypeTextPayload() {
|
|
353
|
-
scannedText = text
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
isScanning = false
|
|
276
|
+
self.lastLink = link
|
|
277
|
+
self.lastText = text
|
|
358
278
|
}
|
|
359
279
|
}
|
|
360
280
|
|
|
361
|
-
nonisolated func readerSession(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
281
|
+
nonisolated func readerSession(_ reader: NFCNDEFReaderSession, didInvalidateWithError error: any Error) {
|
|
282
|
+
let code = (error as? NFCReaderError)?.code
|
|
283
|
+
let quiet = code == .readerSessionInvalidationErrorUserCanceled
|
|
284
|
+
|| code == .readerSessionInvalidationErrorFirstNDEFTagRead
|
|
285
|
+
let message = quiet ? nil : error.localizedDescription
|
|
365
286
|
Task { @MainActor in
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
nfcError?.code != .readerSessionInvalidationErrorFirstNDEFTagRead {
|
|
369
|
-
errorMessage = error.localizedDescription
|
|
370
|
-
}
|
|
371
|
-
isScanning = false
|
|
287
|
+
self.failure = message
|
|
288
|
+
self.isScanning = false
|
|
372
289
|
self.session = nil
|
|
373
290
|
}
|
|
374
291
|
}
|
|
375
292
|
}
|
|
376
293
|
|
|
377
|
-
struct
|
|
378
|
-
@State private var scanner =
|
|
294
|
+
struct StickerScanView: View {
|
|
295
|
+
@State private var scanner = StickerScanner()
|
|
379
296
|
|
|
380
297
|
var body: some View {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
Button("Scan NFC Tag") {
|
|
390
|
-
scanner.startScan()
|
|
391
|
-
}
|
|
392
|
-
.buttonStyle(.borderedProminent)
|
|
393
|
-
.disabled(scanner.isScanning)
|
|
394
|
-
|
|
395
|
-
if let url = scanner.scannedURL {
|
|
396
|
-
Text("URL: \(url.absoluteString)")
|
|
397
|
-
}
|
|
398
|
-
if !scanner.scannedText.isEmpty {
|
|
399
|
-
Text("Text: \(scanner.scannedText)")
|
|
400
|
-
}
|
|
401
|
-
if let error = scanner.errorMessage {
|
|
402
|
-
Text(error).foregroundStyle(.red)
|
|
403
|
-
}
|
|
298
|
+
if scanner.isAvailable {
|
|
299
|
+
VStack(spacing: 12) {
|
|
300
|
+
Button("Scan Sticker") { scanner.start() }
|
|
301
|
+
.buttonStyle(.borderedProminent)
|
|
302
|
+
.disabled(scanner.isScanning)
|
|
303
|
+
if let link = scanner.lastLink { Link(link.absoluteString, destination: link) }
|
|
304
|
+
if let text = scanner.lastText { Text(text) }
|
|
305
|
+
if let failure = scanner.failure { Text(failure).foregroundStyle(.red) }
|
|
404
306
|
}
|
|
307
|
+
} else {
|
|
308
|
+
ContentUnavailableView(
|
|
309
|
+
"NFC Unavailable",
|
|
310
|
+
systemImage: "wave.3.right.circle.fill",
|
|
311
|
+
description: Text("This device cannot read NFC tags.")
|
|
312
|
+
)
|
|
405
313
|
}
|
|
406
|
-
.padding()
|
|
407
314
|
}
|
|
408
315
|
}
|
|
409
316
|
```
|
|
410
317
|
|
|
411
|
-
|
|
318
|
+
The delegate callbacks arrive off the main actor, so they are `nonisolated`
|
|
319
|
+
and hop back with `Task { @MainActor in }` before touching state. Only plain
|
|
320
|
+
values cross that hop.
|
|
412
321
|
|
|
413
|
-
|
|
322
|
+
## Error codes
|
|
414
323
|
|
|
415
|
-
| Code | Meaning |
|
|
416
|
-
|
|
417
|
-
| `.readerSessionInvalidationErrorUserCanceled` | User
|
|
418
|
-
| `.readerSessionInvalidationErrorFirstNDEFTagRead` | Session ended after
|
|
419
|
-
| `.readerSessionInvalidationErrorSessionTimeout` | 60-second session
|
|
420
|
-
| `.readerSessionInvalidationErrorSessionTerminatedUnexpectedly` |
|
|
421
|
-
| `.
|
|
422
|
-
| `.
|
|
423
|
-
| `.
|
|
424
|
-
| `.
|
|
324
|
+
| `NFCReaderError.Code` | Meaning |
|
|
325
|
+
|-----------------------|---------|
|
|
326
|
+
| `.readerSessionInvalidationErrorUserCanceled` | User pressed Cancel |
|
|
327
|
+
| `.readerSessionInvalidationErrorFirstNDEFTagRead` | Session ended after one read because `invalidateAfterFirstRead` was true |
|
|
328
|
+
| `.readerSessionInvalidationErrorSessionTimeout` | The 60-second session limit ran out |
|
|
329
|
+
| `.readerSessionInvalidationErrorSessionTerminatedUnexpectedly` | The system ended the session |
|
|
330
|
+
| `.readerSessionInvalidationErrorSystemIsBusy` | Another NFC session is already running |
|
|
331
|
+
| `.readerTransceiveErrorTagConnectionLost` | The tag left the field mid-exchange |
|
|
332
|
+
| `.readerTransceiveErrorRetryExceeded` | Too many failed attempts |
|
|
333
|
+
| `.readerTransceiveErrorTagNotConnected` | A command was sent before connecting |
|
|
425
334
|
|
|
426
|
-
|
|
335
|
+
A recovery helper that turns codes into user text:
|
|
427
336
|
|
|
428
337
|
```swift
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
errorMessage = "Scan timed out. Try again."
|
|
442
|
-
case .readerTransceiveErrorTagConnectionLost:
|
|
443
|
-
errorMessage = "Tag moved away. Hold steady and try again."
|
|
444
|
-
default:
|
|
445
|
-
errorMessage = error.localizedDescription
|
|
446
|
-
}
|
|
447
|
-
self.session = nil
|
|
338
|
+
func userMessage(for error: Error) -> String? {
|
|
339
|
+
guard let code = (error as? NFCReaderError)?.code else { return error.localizedDescription }
|
|
340
|
+
switch code {
|
|
341
|
+
case .readerSessionInvalidationErrorUserCanceled,
|
|
342
|
+
.readerSessionInvalidationErrorFirstNDEFTagRead:
|
|
343
|
+
return nil
|
|
344
|
+
case .readerSessionInvalidationErrorSessionTimeout:
|
|
345
|
+
return "Scanning took too long. Try again."
|
|
346
|
+
case .readerTransceiveErrorTagConnectionLost:
|
|
347
|
+
return "Keep the tag still until the scan finishes."
|
|
348
|
+
default:
|
|
349
|
+
return error.localizedDescription
|
|
448
350
|
}
|
|
449
351
|
}
|
|
450
352
|
```
|
|
353
|
+
|
|
354
|
+
Whatever the message, clear the stored session in the same callback.
|