@mmerterden/multi-agent-pipeline 20.6.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +328 -329
- package/package.json +2 -2
- package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
- package/pipeline/scripts/_notices.mjs +12 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +106 -106
- package/pipeline/skills/shared/README.md +71 -71
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
- 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/android-architecture/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
- 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/backlog/BACKLOG.md +1 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
- 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/ci-cd-pipelines/SKILL.md +1 -0
- 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/compose-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
- 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/council/SKILL.md +1 -0
- 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/css-modern/SKILL.md +1 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
- 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/evidence-github/SKILL.md +2 -0
- package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
- 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/html-semantic/SKILL.md +1 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
- 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-coding-standard/SKILL.md +1 -0
- 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-module-structure/SKILL.md +1 -0
- 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-security/SKILL.md +2 -0
- 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/localization-reuse-map/SKILL.md +91 -283
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
- 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/nextjs-app-router/SKILL.md +1 -0
- 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/play-store-review/SKILL.md +2 -0
- 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/python-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
- 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/rest-api-design/SKILL.md +1 -0
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
- package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
- 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/signal-community/SKILL.md +2 -0
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
- package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
- package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
- package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
- package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
- package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
- package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
- 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/tailwind-css/SKILL.md +1 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
- 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/typescript-patterns/SKILL.md +1 -0
- 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/vue-composition/SKILL.md +1 -0
- 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/web-accessibility/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
- 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,486 +1,412 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: core-nfc
|
|
3
|
-
description: "
|
|
3
|
+
description: "Core NFC on iPhone: reading and writing NDEF tags, ISO7816, ISO15693, FeliCa and MIFARE access, reader session lifecycle, NFC entitlement and Info.plist setup, background tag reading. Use when adding or reviewing NFC tag scanning or writing."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
8
|
+
# Core NFC
|
|
9
|
+
|
|
10
|
+
Core NFC lets an iPhone app read tags, write NDEF data and talk to a tag in its
|
|
11
|
+
own protocol. Everything happens inside a short-lived reader session that the
|
|
12
|
+
system presents as a scan sheet. Code here assumes Swift 6.3 with an iOS 26
|
|
13
|
+
deployment target, and only iPhone hardware reads tags.
|
|
14
|
+
|
|
15
|
+
## Project setup
|
|
16
|
+
|
|
17
|
+
1. In Signing & Capabilities, add **Near Field Communication Tag Reading**.
|
|
18
|
+
2. Put a sentence the user will read into `NFCReaderUsageDescription` in Info.plist.
|
|
19
|
+
An empty value is a review problem.
|
|
20
|
+
3. The capability writes the reader-session formats entitlement
|
|
21
|
+
(`...nfc.readersession.formats`) with the value `TAG`. Older samples add
|
|
22
|
+
`NDEF` as well; leave that out.
|
|
23
|
+
4. For ISO 7816 cards, list every application identifier the app will select
|
|
24
|
+
in the Info.plist select-identifiers array (shown below).
|
|
25
|
+
5. For FeliCa, list each system code in the Info.plist systemcodes array.
|
|
26
|
+
Wildcard codes are not accepted.
|
|
27
|
+
|
|
28
|
+
```xml
|
|
29
|
+
<key>NFCReaderUsageDescription</key>
|
|
30
|
+
<string>Scanning lets you check in to the gym by tapping your member card.</string>
|
|
31
|
+
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
|
|
32
|
+
<array>
|
|
33
|
+
<string>F0010203040506</string>
|
|
34
|
+
</array>
|
|
35
|
+
<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
|
|
36
|
+
<array>
|
|
37
|
+
<string>12FC</string>
|
|
38
|
+
</array>
|
|
39
|
+
```
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
availability before creating NFC UI or sessions. Use the concrete reader
|
|
40
|
-
session type you are about to create.
|
|
41
|
+
### Hardware and availability
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
Tag reading needs an iPhone 7 or newer. Before showing any NFC button or
|
|
44
|
+
creating a session, ask the session class you intend to use, for example
|
|
45
|
+
`NFCNDEFReaderSession.readingAvailable` or `NFCTagReaderSession.readingAvailable`.
|
|
46
|
+
`false` means the device lacks the hardware or NFC is restricted, so hide or
|
|
47
|
+
disable the feature instead of starting a session that will fail.
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
// Device does not support NFC or feature is restricted
|
|
47
|
-
showUnsupportedMessage()
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Key Types
|
|
49
|
+
### Types you will meet
|
|
53
50
|
|
|
54
51
|
| Type | Role |
|
|
55
|
-
|
|
56
|
-
| `NFCNDEFReaderSession` |
|
|
57
|
-
| `NFCTagReaderSession` |
|
|
58
|
-
| `NFCNDEFMessage` |
|
|
59
|
-
| `NFCNDEFPayload` |
|
|
60
|
-
| `NFCNDEFTag` | Protocol
|
|
52
|
+
|------|------|
|
|
53
|
+
| `NFCNDEFReaderSession` | Session that scans NDEF-formatted tags |
|
|
54
|
+
| `NFCTagReaderSession` | Session with native access to ISO7816, ISO15693, FeliCa and MIFARE tags |
|
|
55
|
+
| `NFCNDEFMessage` | An NDEF message: an ordered list of records |
|
|
56
|
+
| `NFCNDEFPayload` | A single NDEF record |
|
|
57
|
+
| `NFCNDEFTag` | Protocol adopted by tags that can hold NDEF data |
|
|
61
58
|
|
|
62
|
-
## NDEF
|
|
59
|
+
## Reading NDEF data
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
The NDEF session is the shortest path when all you need is a URL, some text or
|
|
62
|
+
a MIME blob.
|
|
66
63
|
|
|
67
64
|
```swift
|
|
68
65
|
import CoreNFC
|
|
69
66
|
|
|
70
|
-
final class
|
|
67
|
+
final class BadgeReader: NSObject, NFCNDEFReaderSessionDelegate {
|
|
71
68
|
private var session: NFCNDEFReaderSession?
|
|
72
69
|
|
|
73
|
-
func
|
|
70
|
+
func scan() {
|
|
74
71
|
guard NFCNDEFReaderSession.readingAvailable else { return }
|
|
75
|
-
|
|
76
72
|
session = NFCNDEFReaderSession(
|
|
77
73
|
delegate: self,
|
|
78
74
|
queue: nil,
|
|
79
75
|
invalidateAfterFirstRead: false
|
|
80
76
|
)
|
|
81
|
-
session?.alertMessage = "Hold your iPhone near
|
|
77
|
+
session?.alertMessage = "Hold the top of your iPhone near the badge."
|
|
82
78
|
session?.begin()
|
|
83
79
|
}
|
|
84
80
|
|
|
85
|
-
|
|
81
|
+
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {}
|
|
86
82
|
|
|
87
|
-
func
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
func readerSession(
|
|
92
|
-
_ session: NFCNDEFReaderSession,
|
|
93
|
-
didDetectNDEFs messages: [NFCNDEFMessage]
|
|
94
|
-
) {
|
|
95
|
-
for message in messages {
|
|
83
|
+
func readerSession(_ reader: NFCNDEFReaderSession, didDetectNDEFs found: [NFCNDEFMessage]) {
|
|
84
|
+
for message in found {
|
|
96
85
|
for record in message.records {
|
|
97
|
-
|
|
86
|
+
handle(record)
|
|
98
87
|
}
|
|
99
88
|
}
|
|
100
89
|
}
|
|
101
90
|
|
|
102
|
-
func readerSession(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
print("Session invalidated: \(error.localizedDescription)")
|
|
91
|
+
func readerSession(_ reader: NFCNDEFReaderSession, didInvalidateWithError error: any Error) {
|
|
92
|
+
if let nfcError = error as? NFCReaderError,
|
|
93
|
+
nfcError.code == .readerSessionInvalidationErrorFirstNDEFTagRead
|
|
94
|
+
|| nfcError.code == .readerSessionInvalidationErrorUserCanceled {
|
|
95
|
+
// Normal end of a scan, not a failure.
|
|
96
|
+
} else {
|
|
97
|
+
report(error)
|
|
110
98
|
}
|
|
111
99
|
self.session = nil
|
|
112
100
|
}
|
|
101
|
+
|
|
102
|
+
private func handle(_ record: NFCNDEFPayload) {}
|
|
103
|
+
private func report(_ error: Error) {}
|
|
113
104
|
}
|
|
114
105
|
```
|
|
115
106
|
|
|
116
|
-
|
|
107
|
+
The first-read and user-cancel codes are ordinary endings. Only other codes
|
|
108
|
+
deserve an alert, and the session reference is cleared in every case.
|
|
109
|
+
|
|
110
|
+
### Connecting to the tag
|
|
117
111
|
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
To read and then write, or to inspect capacity, implement
|
|
113
|
+
`readerSession(_:didDetect:)` with `[any NFCNDEFTag]`. When that method exists,
|
|
114
|
+
the system calls it instead of `didDetectNDEFs`.
|
|
120
115
|
|
|
121
116
|
```swift
|
|
122
|
-
func readerSession(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
) {
|
|
126
|
-
guard let tag = tags.first else {
|
|
127
|
-
session.restartPolling()
|
|
117
|
+
func readerSession(_ reader: NFCNDEFReaderSession, didDetect found: [any NFCNDEFTag]) {
|
|
118
|
+
if found.count > 1 {
|
|
119
|
+
retryAfterMultipleTags(reader)
|
|
128
120
|
return
|
|
129
121
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
122
|
+
guard let first = found.first else {
|
|
123
|
+
reader.restartPolling()
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
nonisolated(unsafe) let reader = reader
|
|
127
|
+
nonisolated(unsafe) let tag = first
|
|
128
|
+
Task {
|
|
129
|
+
do {
|
|
130
|
+
try await reader.connect(to: tag)
|
|
131
|
+
let (status, _) = try await tag.queryNDEFStatus()
|
|
143
132
|
switch status {
|
|
144
133
|
case .notSupported:
|
|
145
|
-
|
|
146
|
-
case .readOnly:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
session.invalidate()
|
|
152
|
-
}
|
|
153
|
-
case .readWrite:
|
|
154
|
-
tag.readNDEF { message, error in
|
|
155
|
-
if let message {
|
|
156
|
-
self.processMessage(message)
|
|
157
|
-
}
|
|
158
|
-
session.alertMessage = "Tag read successfully."
|
|
159
|
-
session.invalidate()
|
|
160
|
-
}
|
|
134
|
+
reader.invalidate(errorMessage: "No NDEF support on this tag.")
|
|
135
|
+
case .readOnly, .readWrite:
|
|
136
|
+
let badge = try await tag.readNDEF()
|
|
137
|
+
reader.alertMessage = "Badge read: \(badge.records.count) records."
|
|
138
|
+
reader.invalidate()
|
|
161
139
|
@unknown default:
|
|
162
|
-
|
|
140
|
+
reader.invalidate(errorMessage: "Unexpected tag status.")
|
|
163
141
|
}
|
|
142
|
+
} catch {
|
|
143
|
+
reader.invalidate(errorMessage: "Could not read the tag: \(error.localizedDescription)")
|
|
164
144
|
}
|
|
165
145
|
}
|
|
166
146
|
}
|
|
167
147
|
```
|
|
168
148
|
|
|
169
|
-
|
|
149
|
+
Every tag call also has a completion-handler form. Those handlers are
|
|
150
|
+
`@Sendable`, while sessions and tags are not `Sendable`, so capturing them there
|
|
151
|
+
draws Swift 6 warnings. The `async` forms, awaited inside one `Task`, keep the
|
|
152
|
+
steps in reading order, but the `Task` closure still captures the session and
|
|
153
|
+
the tag. Swift 6 rejects that in its region-isolation pass ("passing closure as
|
|
154
|
+
a 'sending' parameter risks causing data races"), so the callback hands them
|
|
155
|
+
over as `nonisolated(unsafe)` copies. After starting the task the callback
|
|
156
|
+
touches neither, which leaves the task as their only user.
|
|
157
|
+
|
|
158
|
+
An empty array means nothing usable was found yet: `restartPolling()` keeps
|
|
159
|
+
the sheet open and waits for the next tap.
|
|
160
|
+
|
|
161
|
+
### More than one tag in the field
|
|
162
|
+
|
|
163
|
+
Both session types can report several tags at once. Talking to one of them
|
|
164
|
+
while another is in range is unreliable, so do not pick one at random. Tell the
|
|
165
|
+
user to take the extra tags away, pause briefly so they can do it, then resume
|
|
166
|
+
polling on the same session:
|
|
167
|
+
|
|
168
|
+
```swift
|
|
169
|
+
func retryAfterMultipleTags(_ session: NFCNDEFReaderSession) {
|
|
170
|
+
session.alertMessage = "More than one tag found. Remove the extras and try again."
|
|
171
|
+
nonisolated(unsafe) let session = session
|
|
172
|
+
Task {
|
|
173
|
+
try? await Task.sleep(for: .milliseconds(500))
|
|
174
|
+
session.restartPolling()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
func retryAfterMultipleTags(_ session: NFCTagReaderSession) {
|
|
179
|
+
session.alertMessage = "More than one tag found. Remove the extras and try again."
|
|
180
|
+
nonisolated(unsafe) let session = session
|
|
181
|
+
Task {
|
|
182
|
+
try? await Task.sleep(for: .milliseconds(500))
|
|
183
|
+
session.restartPolling()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`restartPolling()` is declared separately on each session class rather than on
|
|
189
|
+
the shared base, hence the two overloads. A zero-length array gets the plain
|
|
190
|
+
`restartPolling()` call without the message.
|
|
191
|
+
|
|
192
|
+
## Native tag access
|
|
193
|
+
|
|
194
|
+
`NFCTagReaderSession` exposes each tag family in its own protocol: ISO 7816,
|
|
195
|
+
ISO 15693, FeliCa and MIFARE. Pick polling options to match the tags you expect:
|
|
170
196
|
|
|
171
|
-
|
|
172
|
-
|
|
197
|
+
| Polling option | Finds |
|
|
198
|
+
|----------------|-------|
|
|
199
|
+
| `.iso14443` | ISO 7816 compatible cards and MIFARE |
|
|
200
|
+
| `.iso15693` | ISO 15693 vicinity tags |
|
|
201
|
+
| `.iso18092` | FeliCa |
|
|
173
202
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
`.
|
|
177
|
-
payment-related AIDs; Apple documents `NFCPaymentTagReaderSession` for
|
|
178
|
-
eligible EU payment use cases.
|
|
203
|
+
Payment application identifiers are off limits for this session. For eligible
|
|
204
|
+
payment use cases in the EU, Apple documents a separate
|
|
205
|
+
`NFCPaymentTagReaderSession`.
|
|
179
206
|
|
|
180
207
|
```swift
|
|
181
|
-
final class
|
|
208
|
+
final class CardReader: NSObject, NFCTagReaderSessionDelegate {
|
|
182
209
|
private var session: NFCTagReaderSession?
|
|
183
210
|
|
|
184
|
-
func
|
|
211
|
+
func scan() {
|
|
185
212
|
guard NFCTagReaderSession.readingAvailable else { return }
|
|
186
|
-
|
|
187
|
-
session =
|
|
188
|
-
pollingOption: [.iso14443, .iso15693, .iso18092],
|
|
189
|
-
delegate: self,
|
|
190
|
-
queue: nil
|
|
191
|
-
)
|
|
192
|
-
session?.alertMessage = "Hold your iPhone near a tag."
|
|
213
|
+
session = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693], delegate: self, queue: nil)
|
|
214
|
+
session?.alertMessage = "Hold your transit card near the phone."
|
|
193
215
|
session?.begin()
|
|
194
216
|
}
|
|
195
217
|
|
|
196
|
-
func tagReaderSessionDidBecomeActive(
|
|
197
|
-
|
|
198
|
-
) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
218
|
+
func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {}
|
|
219
|
+
|
|
220
|
+
func tagReaderSession(_ reader: NFCTagReaderSession, didDetect found: [NFCTag]) {
|
|
221
|
+
if found.count > 1 {
|
|
222
|
+
retryAfterMultipleTags(reader)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
guard !found.isEmpty else {
|
|
226
|
+
reader.restartPolling()
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
nonisolated(unsafe) let reader = reader
|
|
230
|
+
nonisolated(unsafe) let tags = found
|
|
231
|
+
Task {
|
|
232
|
+
let tag = tags[0]
|
|
233
|
+
do {
|
|
234
|
+
try await reader.connect(to: tag)
|
|
235
|
+
} catch {
|
|
236
|
+
reader.invalidate(errorMessage: error.localizedDescription)
|
|
211
237
|
return
|
|
212
238
|
}
|
|
213
|
-
|
|
214
239
|
switch tag {
|
|
215
|
-
case .iso7816(let
|
|
216
|
-
|
|
217
|
-
case .
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
self.readISO15693(tag: iso15693Tag, session: session)
|
|
221
|
-
case .feliCa(let feliCaTag):
|
|
222
|
-
self.readFeliCa(tag: feliCaTag, session: session)
|
|
223
|
-
@unknown default:
|
|
224
|
-
session.invalidate(errorMessage: "Unsupported tag type.")
|
|
240
|
+
case .iso7816(let card): await select(card, in: reader)
|
|
241
|
+
case .miFare(let chip): await inspect(chip, in: reader)
|
|
242
|
+
case .iso15693(let label): await readBlocks(label, in: reader)
|
|
243
|
+
case .feliCa(let card): await readSystemCodes(card, in: reader)
|
|
244
|
+
@unknown default: reader.invalidate(errorMessage: "This tag type is not handled.")
|
|
225
245
|
}
|
|
226
246
|
}
|
|
227
247
|
}
|
|
228
248
|
|
|
229
|
-
func tagReaderSession(
|
|
230
|
-
|
|
231
|
-
didInvalidateWithError error: Error
|
|
232
|
-
) {
|
|
233
|
-
self.session = nil
|
|
249
|
+
func tagReaderSession(_ reader: NFCTagReaderSession, didInvalidateWithError error: any Error) {
|
|
250
|
+
session = nil
|
|
234
251
|
}
|
|
235
252
|
}
|
|
236
253
|
```
|
|
237
254
|
|
|
238
|
-
|
|
255
|
+
The task receives the whole `tags` array rather than a single `NFCTag`: with
|
|
256
|
+
the enum value itself as the `nonisolated(unsafe)` copy, Swift 6.3 still
|
|
257
|
+
rejects the capture. Always connect before sending any command. The `select`, `inspect`,
|
|
258
|
+
`readBlocks` and `readSystemCodes` helpers stand for per-family code (APDUs,
|
|
259
|
+
block reads, MIFARE and FeliCa calls), which is written out in the
|
|
260
|
+
[patterns reference](references/nfc-patterns.md).
|
|
239
261
|
|
|
240
|
-
|
|
262
|
+
## Writing NDEF
|
|
263
|
+
|
|
264
|
+
Query the status first. Only `.readWrite` tags accept a write; anything else
|
|
265
|
+
ends the session with a clear message.
|
|
241
266
|
|
|
242
267
|
```swift
|
|
243
|
-
func
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
url: URL
|
|
247
|
-
) {
|
|
248
|
-
tag.queryNDEFStatus { status, capacity, error in
|
|
268
|
+
func store(_ link: URL, on tag: any NFCNDEFTag, session: NFCNDEFReaderSession) async {
|
|
269
|
+
do {
|
|
270
|
+
let (status, _) = try await tag.queryNDEFStatus()
|
|
249
271
|
guard status == .readWrite else {
|
|
250
|
-
session.invalidate(errorMessage: "
|
|
272
|
+
session.invalidate(errorMessage: "This tag is read-only.")
|
|
251
273
|
return
|
|
252
274
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
url: url
|
|
256
|
-
) else {
|
|
257
|
-
session.invalidate(errorMessage: "Invalid URL.")
|
|
275
|
+
guard let record = NFCNDEFPayload.wellKnownTypeURIPayload(url: link) else {
|
|
276
|
+
session.invalidate(errorMessage: "Could not encode the link.")
|
|
258
277
|
return
|
|
259
278
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
session.invalidate(
|
|
266
|
-
errorMessage: "Write failed: \(error.localizedDescription)"
|
|
267
|
-
)
|
|
268
|
-
} else {
|
|
269
|
-
session.alertMessage = "Tag written successfully."
|
|
270
|
-
session.invalidate()
|
|
271
|
-
}
|
|
272
|
-
}
|
|
279
|
+
try await tag.writeNDEF(NFCNDEFMessage(records: [record]))
|
|
280
|
+
session.alertMessage = "Tag updated."
|
|
281
|
+
session.invalidate()
|
|
282
|
+
} catch {
|
|
283
|
+
session.invalidate(errorMessage: "Could not write: \(error.localizedDescription)")
|
|
273
284
|
}
|
|
274
285
|
}
|
|
275
286
|
```
|
|
276
287
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
### Creating Common Payloads
|
|
288
|
+
Check capacity against the message size before large writes; see the reference
|
|
289
|
+
file for the comparison and for permanent locking.
|
|
280
290
|
|
|
281
|
-
|
|
282
|
-
// URL payload
|
|
283
|
-
let urlPayload = NFCNDEFPayload.wellKnownTypeURIPayload(
|
|
284
|
-
url: URL(string: "https://example.com")!
|
|
285
|
-
)
|
|
291
|
+
## Building and parsing payloads
|
|
286
292
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
)
|
|
293
|
+
| Record | Factory |
|
|
294
|
+
|--------|---------|
|
|
295
|
+
| URI | `NFCNDEFPayload.wellKnownTypeURIPayload(url:)` (optional result) |
|
|
296
|
+
| Text | `NFCNDEFPayload.wellKnownTypeTextPayload(string:locale:)` |
|
|
297
|
+
| App data | `NFCNDEFPayload(format: .nfcExternal, type:identifier:payload:)` with a `domain:type` type string |
|
|
292
298
|
|
|
293
|
-
|
|
294
|
-
let
|
|
299
|
+
```swift
|
|
300
|
+
let config = NFCNDEFPayload(
|
|
295
301
|
format: .nfcExternal,
|
|
296
|
-
type: "com
|
|
302
|
+
type: Data("example.com:shelf".utf8),
|
|
297
303
|
identifier: Data(),
|
|
298
|
-
payload: "
|
|
304
|
+
payload: Data(#"{"aisle":7}"#.utf8)
|
|
299
305
|
)
|
|
300
306
|
```
|
|
301
307
|
|
|
302
|
-
|
|
308
|
+
When reading, branch on `typeNameFormat`:
|
|
303
309
|
|
|
304
310
|
```swift
|
|
305
|
-
func
|
|
311
|
+
func describe(_ record: NFCNDEFPayload) -> String? {
|
|
306
312
|
switch record.typeNameFormat {
|
|
307
313
|
case .nfcWellKnown:
|
|
308
|
-
if let url = record.wellKnownTypeURIPayload() {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
print("Text (\(locale)): \(text)")
|
|
312
|
-
}
|
|
314
|
+
if let url = record.wellKnownTypeURIPayload() { return url.absoluteString }
|
|
315
|
+
let (text, locale) = record.wellKnownTypeTextPayload()
|
|
316
|
+
return text.map { "\($0) [\(locale?.identifier ?? "?")]" }
|
|
313
317
|
case .absoluteURI:
|
|
314
|
-
|
|
315
|
-
print("Absolute URI: \(uri)")
|
|
316
|
-
}
|
|
318
|
+
return utf8(record.payload)
|
|
317
319
|
case .media:
|
|
318
|
-
|
|
319
|
-
print("MIME type: \(mimeType), size: \(record.payload.count)")
|
|
320
|
+
return "\(utf8(record.type) ?? "unknown"), \(record.payload.count) bytes"
|
|
320
321
|
case .nfcExternal:
|
|
321
|
-
|
|
322
|
-
print("External type: \(type)")
|
|
322
|
+
return utf8(record.type)
|
|
323
323
|
case .empty, .unknown, .unchanged:
|
|
324
|
-
|
|
324
|
+
return nil
|
|
325
325
|
@unknown default:
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
## Background Tag Reading
|
|
332
|
-
|
|
333
|
-
On iPhone XS and later, iOS can read NFC tags in the background without
|
|
334
|
-
opening your app. The NDEF message must contain a URI record
|
|
335
|
-
(`typeNameFormat == .nfcWellKnown`, type `U`). If there are multiple URI
|
|
336
|
-
records, the system uses the first one.
|
|
337
|
-
|
|
338
|
-
For app-specific routing, write a universal link to the tag and configure the
|
|
339
|
-
Associated Domains capability for that domain. Background tag reading also
|
|
340
|
-
supports specific system URL schemes such as web, email, SMS, telephone,
|
|
341
|
-
FaceTime, Maps, and HomeKit setup. It does not support custom URL schemes, and
|
|
342
|
-
the system does not route by bundle ID or arbitrary NDEF content type.
|
|
343
|
-
|
|
344
|
-
When a user taps a compatible tag, iOS displays a notification that opens
|
|
345
|
-
your app. Handle the tag data via `NSUserActivity`:
|
|
346
|
-
|
|
347
|
-
```swift
|
|
348
|
-
func scene(
|
|
349
|
-
_ scene: UIScene,
|
|
350
|
-
continue userActivity: NSUserActivity
|
|
351
|
-
) {
|
|
352
|
-
guard userActivity.activityType ==
|
|
353
|
-
NSUserActivityTypeBrowsingWeb else { return }
|
|
354
|
-
|
|
355
|
-
let message = userActivity.ndefMessagePayload
|
|
356
|
-
guard message.records.first?.typeNameFormat != .empty else { return }
|
|
357
|
-
|
|
358
|
-
for record in message.records {
|
|
359
|
-
processRecord(record)
|
|
326
|
+
return nil
|
|
360
327
|
}
|
|
361
328
|
}
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
## Common Mistakes
|
|
365
|
-
|
|
366
|
-
### DON'T: Use stale or missing NFC entitlements
|
|
367
|
-
|
|
368
|
-
Without the `com.apple.developer.nfc.readersession.formats` entitlement,
|
|
369
|
-
reader sessions cannot access NFC hardware. Use the current `TAG` value for
|
|
370
|
-
Core NFC reader sessions; do not copy older examples that add `NDEF`.
|
|
371
|
-
|
|
372
|
-
### DON'T: Skip the readingAvailable check
|
|
373
|
-
|
|
374
|
-
Creating an NFC session on an unsupported or restricted device fails before
|
|
375
|
-
the scan UI can do useful work.
|
|
376
|
-
|
|
377
|
-
Check `NFCNDEFReaderSession.readingAvailable` or
|
|
378
|
-
`NFCTagReaderSession.readingAvailable` before creating the matching session.
|
|
379
|
-
|
|
380
|
-
### DON'T: Ignore session invalidation errors
|
|
381
|
-
|
|
382
|
-
The session invalidates for multiple reasons. Distinguishing user cancellation
|
|
383
|
-
from real errors prevents false error alerts.
|
|
384
|
-
|
|
385
|
-
```swift
|
|
386
|
-
// WRONG -- shows error when user cancels
|
|
387
|
-
func readerSession(
|
|
388
|
-
_ session: NFCNDEFReaderSession,
|
|
389
|
-
didInvalidateWithError error: Error
|
|
390
|
-
) {
|
|
391
|
-
showAlert("NFC Error: \(error.localizedDescription)")
|
|
392
|
-
}
|
|
393
329
|
|
|
394
|
-
|
|
395
|
-
func readerSession(
|
|
396
|
-
_ session: NFCNDEFReaderSession,
|
|
397
|
-
didInvalidateWithError error: Error
|
|
398
|
-
) {
|
|
399
|
-
let nfcError = error as? NFCReaderError
|
|
400
|
-
switch nfcError?.code {
|
|
401
|
-
case .readerSessionInvalidationErrorUserCanceled,
|
|
402
|
-
.readerSessionInvalidationErrorFirstNDEFTagRead:
|
|
403
|
-
break // Normal termination
|
|
404
|
-
default:
|
|
405
|
-
showAlert("NFC Error: \(error.localizedDescription)")
|
|
406
|
-
}
|
|
407
|
-
self.session = nil
|
|
408
|
-
}
|
|
330
|
+
func utf8(_ bytes: Data) -> String? { String(bytes: bytes, encoding: .utf8) }
|
|
409
331
|
```
|
|
410
332
|
|
|
411
|
-
|
|
333
|
+
## Background tag reading
|
|
412
334
|
|
|
413
|
-
|
|
414
|
-
and create a new session for the next scan.
|
|
335
|
+
iPhone XS and newer read tags without the app open. The rules:
|
|
415
336
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
session = NFCNDEFReaderSession(
|
|
425
|
-
delegate: self, queue: nil, invalidateAfterFirstRead: false
|
|
426
|
-
)
|
|
427
|
-
session?.begin()
|
|
428
|
-
}
|
|
429
|
-
```
|
|
337
|
+
- One record has to be a well-known URI record: `typeNameFormat` equal to
|
|
338
|
+
`.nfcWellKnown` and a record type of `U`. If there are several URI records, the first one wins.
|
|
339
|
+
- To route to your app, write a universal link and set up Associated Domains
|
|
340
|
+
for that domain.
|
|
341
|
+
- System schemes also work: web, mail, SMS, phone, FaceTime, Maps and HomeKit
|
|
342
|
+
setup.
|
|
343
|
+
- Custom URL schemes do nothing here. There is no routing by bundle identifier
|
|
344
|
+
or by arbitrary NDEF content type.
|
|
430
345
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
Writing to a read-only tag silently fails or produces confusing errors.
|
|
346
|
+
A matching tag posts a notification; tapping it launches the app with a
|
|
347
|
+
browsing-web activity:
|
|
434
348
|
|
|
435
349
|
```swift
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
tag.queryNDEFStatus { status, capacity, error in
|
|
443
|
-
guard status == .readWrite else {
|
|
444
|
-
session.invalidate(errorMessage: "Tag is read-only.")
|
|
445
|
-
return
|
|
446
|
-
}
|
|
447
|
-
tag.writeNDEF(message) { error in
|
|
448
|
-
// Handle result
|
|
350
|
+
func scene(_ scene: UIScene, continue activity: NSUserActivity) {
|
|
351
|
+
guard activity.activityType == NSUserActivityTypeBrowsingWeb else { return }
|
|
352
|
+
let message = activity.ndefMessagePayload
|
|
353
|
+
guard let first = message.records.first, first.typeNameFormat != .empty else { return }
|
|
354
|
+
for record in message.records {
|
|
355
|
+
route(record)
|
|
449
356
|
}
|
|
450
357
|
}
|
|
451
358
|
```
|
|
452
359
|
|
|
453
|
-
##
|
|
454
|
-
|
|
455
|
-
-
|
|
456
|
-
|
|
457
|
-
-
|
|
458
|
-
|
|
459
|
-
-
|
|
460
|
-
|
|
461
|
-
-
|
|
462
|
-
|
|
463
|
-
-
|
|
464
|
-
|
|
465
|
-
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
360
|
+
## Mistakes that keep coming back
|
|
361
|
+
|
|
362
|
+
- **Wrong or missing formats entitlement.** Without it the session cannot reach
|
|
363
|
+
the NFC hardware. Use `TAG`; drop copied `NDEF` values.
|
|
364
|
+
- **No `readingAvailable` check.** Where NFC is missing or switched off by
|
|
365
|
+
policy, session creation fails before any scan sheet appears.
|
|
366
|
+
- **Every invalidation treated as an error.** Cancel and first-read are normal;
|
|
367
|
+
alerting on them trains users to ignore alerts.
|
|
368
|
+
- **Reusing a dead session.** An invalidated session cannot restart and
|
|
369
|
+
`begin()` on it does nothing. Clear the reference and create a new one.
|
|
370
|
+
- **Writing blind.** Without a status query, writes to read-only tags fail in
|
|
371
|
+
confusing ways.
|
|
372
|
+
- **Answering the first of several tags.** Ask the user to remove the extras
|
|
373
|
+
and restart polling.
|
|
374
|
+
|
|
375
|
+
## Review checklist
|
|
376
|
+
|
|
377
|
+
- [ ] NFC Tag Reading capability is on
|
|
378
|
+
- [ ] `NFCReaderUsageDescription` has a real sentence
|
|
379
|
+
- [ ] Formats entitlement is `TAG`, with no `NDEF`
|
|
380
|
+
- [ ] `readingAvailable` is checked on the same session class that is created
|
|
381
|
+
- [ ] Delegate is supplied before `begin()`
|
|
382
|
+
- [ ] Session reference is cleared after invalidation
|
|
383
|
+
- [ ] `didInvalidateWithError` separates cancel and first-read from real errors
|
|
384
|
+
- [ ] Multiple detected tags lead to a remove-extras message and `restartPolling()`
|
|
385
|
+
- [ ] NDEF status is queried before any write
|
|
386
|
+
- [ ] Capacity is compared with message length before large writes
|
|
387
|
+
- [ ] ISO 7816 AIDs are listed when `NFCTagReaderSession` selects applications
|
|
388
|
+
- [ ] FeliCa system codes are listed when polling `.iso18092`
|
|
389
|
+
- [ ] Background tags carry a URI record with a universal link or supported system scheme
|
|
390
|
+
- [ ] No custom schemes, bundle IDs or content types relied on for background routing
|
|
391
|
+
- [ ] Payment AIDs are kept away from `NFCTagReaderSession`
|
|
392
|
+
- [ ] Only one reader session runs at a time
|
|
470
393
|
|
|
471
394
|
## References
|
|
472
395
|
|
|
473
|
-
-
|
|
474
|
-
-
|
|
475
|
-
|
|
476
|
-
-
|
|
477
|
-
- [
|
|
478
|
-
- [
|
|
479
|
-
|
|
480
|
-
- [
|
|
481
|
-
|
|
482
|
-
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
- [
|
|
486
|
-
|
|
396
|
+
- [NFC patterns](references/nfc-patterns.md): ISO 7816 APDUs, ISO 15693
|
|
397
|
+
blocks, MIFARE and FeliCa commands, multi-tag handling, multi-record messages
|
|
398
|
+
and capacity, tag locking, a SwiftUI scanner, error codes
|
|
399
|
+
- Apple documentation, all under developer.apple.com/documentation:
|
|
400
|
+
- [framework overview](https://developer.apple.com/documentation/corenfc)
|
|
401
|
+
- session classes: [NDEF](https://developer.apple.com/documentation/corenfc/nfcndefreadersession),
|
|
402
|
+
[tag](https://developer.apple.com/documentation/corenfc/nfctagreadersession)
|
|
403
|
+
- delegates: [NDEF](https://developer.apple.com/documentation/corenfc/nfcndefreadersessiondelegate),
|
|
404
|
+
[tag](https://developer.apple.com/documentation/corenfc/nfctagreadersessiondelegate)
|
|
405
|
+
- data types: [message](https://developer.apple.com/documentation/corenfc/nfcndefmessage),
|
|
406
|
+
[payload](https://developer.apple.com/documentation/corenfc/nfcndefpayload),
|
|
407
|
+
[NDEF tag protocol](https://developer.apple.com/documentation/corenfc/nfcndeftag)
|
|
408
|
+
- articles: [tag-reader sample app](https://developer.apple.com/documentation/corenfc/building-an-nfc-tag-reader-app),
|
|
409
|
+
[background reading](https://developer.apple.com/documentation/corenfc/adding-support-for-background-tag-reading)
|
|
410
|
+
- keys: [formats entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.nfc.readersession.formats),
|
|
411
|
+
[ISO 7816 AIDs](https://developer.apple.com/documentation/bundleresources/information-property-list/com.apple.developer.nfc.readersession.iso7816.select-identifiers),
|
|
412
|
+
[FeliCa system codes](https://developer.apple.com/documentation/bundleresources/information-property-list/com.apple.developer.nfc.readersession.felica.systemcodes)
|