@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1
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 +21 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +285 -285
- package/package.json +3 -3
- package/pipeline/lib/redact.mjs +3 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/scripts/gen-skills-index.mjs +13 -1
- package/pipeline/scripts/pre-commit-check.sh +4 -0
- package/pipeline/skills/.skill-manifest.json +69 -69
- 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 +345 -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 +107 -121
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -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 +335 -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 +277 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -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 +274 -384
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
- 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 +228 -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 +302 -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 +236 -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/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -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/skill-creator/template.md +7 -1
- 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 +302 -241
- 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 +304 -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 +183 -162
- 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 +411 -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 +375 -491
- 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 +397 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -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 +201 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
- 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 +160 -315
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,498 +1,350 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: core-bluetooth
|
|
3
|
-
description: "
|
|
3
|
+
description: "Direct BLE with Core Bluetooth: central and peripheral GATT communication, CBCentralManager scanning and connecting, service and characteristic discovery, CBPeripheral reads, writes and subscriptions, CBPeripheralManager local services, Bluetooth authorization, background BLE modes, state restoration, write flow control, CBUUID workflows. Use when an app talks to BLE accessories or acts as one. Not for privacy-preserving accessory setup or picker flows: AccessorySetupKit first, then GATT here."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Core Bluetooth
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- [Setup](#setup)
|
|
20
|
-
- [Central Role: Scanning](#central-role-scanning)
|
|
21
|
-
- [Central Role: Connecting](#central-role-connecting)
|
|
22
|
-
- [Discovering Services and Characteristics](#discovering-services-and-characteristics)
|
|
23
|
-
- [Reading, Writing, and Notifications](#reading-writing-and-notifications)
|
|
24
|
-
- [Peripheral Role: Advertising](#peripheral-role-advertising)
|
|
25
|
-
- [Background BLE](#background-ble)
|
|
26
|
-
- [State Restoration](#state-restoration)
|
|
27
|
-
- [Common Mistakes](#common-mistakes)
|
|
28
|
-
- [Review Checklist](#review-checklist)
|
|
29
|
-
- [References](#references)
|
|
10
|
+
Core Bluetooth lets an app act as a BLE **central** (scan, connect, read and
|
|
11
|
+
write another device) or as a **peripheral** (publish services others connect
|
|
12
|
+
to). This skill covers both roles, background modes and state restoration, for
|
|
13
|
+
Swift 6.3 and iOS 26.
|
|
14
|
+
|
|
15
|
+
Finding and pairing an accessory through the system picker, without broad
|
|
16
|
+
Bluetooth permission, is AccessorySetupKit's job. Once the user has picked the
|
|
17
|
+
accessory, the GATT conversation (services, characteristics, reads, writes,
|
|
18
|
+
notifications) happens here.
|
|
30
19
|
|
|
31
20
|
## Setup
|
|
32
21
|
|
|
33
|
-
### Info.plist
|
|
22
|
+
### Info.plist
|
|
34
23
|
|
|
35
24
|
| Key | Purpose |
|
|
36
|
-
|
|
37
|
-
| `NSBluetoothAlwaysUsageDescription` | Required.
|
|
38
|
-
| `UIBackgroundModes` with `bluetooth-central` |
|
|
39
|
-
| `UIBackgroundModes` with `bluetooth-peripheral` |
|
|
25
|
+
|-----|---------|
|
|
26
|
+
| `NSBluetoothAlwaysUsageDescription` | Required. Tells the user why the app uses Bluetooth |
|
|
27
|
+
| `UIBackgroundModes` with `bluetooth-central` | Scan and stay connected in the background |
|
|
28
|
+
| `UIBackgroundModes` with `bluetooth-peripheral` | Advertise and serve in the background |
|
|
40
29
|
|
|
41
|
-
###
|
|
30
|
+
### Permission and power state
|
|
42
31
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Treat `.denied` and `.restricted` as terminal until the user changes Settings;
|
|
47
|
-
wait for `.poweredOn` before scanning, connecting, advertising, or publishing
|
|
48
|
-
services.
|
|
32
|
+
There is no "request Bluetooth permission" call. The system asks the first
|
|
33
|
+
time the app creates a manager, so create one only when the user reaches the
|
|
34
|
+
feature that needs it. Then look at two things:
|
|
49
35
|
|
|
50
|
-
|
|
36
|
+
- `CBManager.authorization` (a class property from iOS 13.1; the instance
|
|
37
|
+
property was deprecated after 13.0): `.denied` and `.restricted` stay that way
|
|
38
|
+
until the user changes Settings, so show guidance rather than retrying. Being a
|
|
39
|
+
class property, it can be read before any manager is created.
|
|
40
|
+
- `manager.state`: do nothing (scan, connect, advertise, add services) until it
|
|
41
|
+
is `.poweredOn`.
|
|
51
42
|
|
|
52
|
-
|
|
43
|
+
## Central role
|
|
53
44
|
|
|
54
|
-
|
|
45
|
+
### Scanning
|
|
55
46
|
|
|
56
47
|
```swift
|
|
57
48
|
import CoreBluetooth
|
|
58
49
|
|
|
59
|
-
final class
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
final class ScaleLink: NSObject, CBCentralManagerDelegate {
|
|
51
|
+
static var weightService: CBUUID { CBUUID(string: "181D") }
|
|
52
|
+
|
|
53
|
+
private var central: CBCentralManager!
|
|
54
|
+
private var scale: CBPeripheral?
|
|
62
55
|
|
|
63
56
|
override init() {
|
|
64
57
|
super.init()
|
|
65
|
-
|
|
58
|
+
central = CBCentralManager(delegate: self, queue: nil)
|
|
66
59
|
}
|
|
67
60
|
|
|
68
61
|
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
69
62
|
guard central.state == .poweredOn else { return }
|
|
70
|
-
|
|
63
|
+
central.scanForPeripherals(
|
|
64
|
+
withServices: [Self.weightService],
|
|
65
|
+
options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func centralManager(_ central: CBCentralManager, didDiscover found: CBPeripheral,
|
|
70
|
+
advertisementData: [String: Any], rssi: NSNumber) {
|
|
71
|
+
guard rssi.intValue > -75 else { return }
|
|
72
|
+
scale = found
|
|
73
|
+
central.stopScan()
|
|
74
|
+
central.connect(found)
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
|
|
79
|
+
- Scan for the service UUIDs you care about. `withServices: nil` finds
|
|
80
|
+
everything, burns battery and has no place in a shipping app.
|
|
81
|
+
- Ignoring weak signals (here anything at or below -75 dBm) is optional.
|
|
82
|
+
- Keep a strong reference to the discovered `CBPeripheral`. Core Bluetooth
|
|
83
|
+
does not, and a released peripheral silently never connects.
|
|
84
|
+
- Stop scanning as soon as the target turns up, then connect.
|
|
76
85
|
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
The implicitly unwrapped `central` exists only because `self` must be fully
|
|
87
|
+
initialized before it can be the delegate. `CBUUID` is not `Sendable`, and
|
|
88
|
+
Swift 6 rejects a stored `static let` of it on a type without actor isolation,
|
|
89
|
+
so the UUIDs are computed static properties. A `@MainActor` type can keep
|
|
90
|
+
stored ones, as the SwiftUI monitor in the reference does.
|
|
79
91
|
|
|
80
|
-
|
|
81
|
-
let heartRateServiceUUID = CBUUID(string: "180D")
|
|
82
|
-
|
|
83
|
-
func startScanning() {
|
|
84
|
-
centralManager.scanForPeripherals(
|
|
85
|
-
withServices: [heartRateServiceUUID],
|
|
86
|
-
options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
func centralManager(
|
|
91
|
-
_ central: CBCentralManager,
|
|
92
|
-
didDiscover peripheral: CBPeripheral,
|
|
93
|
-
advertisementData: [String: Any],
|
|
94
|
-
rssi RSSI: NSNumber
|
|
95
|
-
) {
|
|
96
|
-
guard RSSI.intValue > -70 else { return } // Filter weak signals
|
|
97
|
-
|
|
98
|
-
// IMPORTANT: Retain the peripheral -- it will be deallocated otherwise
|
|
99
|
-
discoveredPeripheral = peripheral
|
|
100
|
-
centralManager.stopScan()
|
|
101
|
-
centralManager.connect(peripheral, options: nil)
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Central Role: Connecting
|
|
92
|
+
### Connecting and disconnecting
|
|
106
93
|
|
|
107
94
|
```swift
|
|
108
|
-
|
|
109
|
-
_ central: CBCentralManager,
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
peripheral.discoverServices([heartRateServiceUUID])
|
|
114
|
-
}
|
|
95
|
+
extension ScaleLink {
|
|
96
|
+
func centralManager(_ central: CBCentralManager, didConnect device: CBPeripheral) {
|
|
97
|
+
device.delegate = self
|
|
98
|
+
device.discoverServices([Self.weightService])
|
|
99
|
+
}
|
|
115
100
|
|
|
116
|
-
func centralManager(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
isReconnecting: Bool,
|
|
121
|
-
error: Error?
|
|
122
|
-
) {
|
|
123
|
-
if isReconnecting {
|
|
124
|
-
// System is automatically reconnecting
|
|
125
|
-
return
|
|
101
|
+
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral device: CBPeripheral,
|
|
102
|
+
timestamp: CFAbsoluteTime, isReconnecting: Bool, error: (any Error)?) {
|
|
103
|
+
if isReconnecting { return }
|
|
104
|
+
scale = nil
|
|
126
105
|
}
|
|
127
|
-
// Handle disconnection -- optionally reconnect
|
|
128
|
-
discoveredPeripheral = nil
|
|
129
106
|
}
|
|
130
107
|
```
|
|
131
108
|
|
|
132
|
-
|
|
109
|
+
Set the peripheral's delegate before asking it for anything. When
|
|
110
|
+
`isReconnecting` is true the system is already reconnecting on your behalf;
|
|
111
|
+
otherwise decide whether to reconnect yourself and release the reference.
|
|
133
112
|
|
|
134
|
-
|
|
113
|
+
## Discovering services and characteristics
|
|
135
114
|
|
|
136
115
|
```swift
|
|
137
|
-
extension
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
for service in services {
|
|
144
|
-
peripheral.discoverCharacteristics(nil, for: service)
|
|
116
|
+
extension ScaleLink: CBPeripheralDelegate {
|
|
117
|
+
static var weightMeasurement: CBUUID { CBUUID(string: "2A9D") }
|
|
118
|
+
|
|
119
|
+
func peripheral(_ device: CBPeripheral, didDiscoverServices error: (any Error)?) {
|
|
120
|
+
for service in device.services ?? [] {
|
|
121
|
+
device.discoverCharacteristics([Self.weightMeasurement, CBUUID(string: "2A19")], for: service)
|
|
145
122
|
}
|
|
146
123
|
}
|
|
147
124
|
|
|
148
|
-
func peripheral(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
) {
|
|
153
|
-
guard let characteristics = service.characteristics else { return }
|
|
154
|
-
for characteristic in characteristics {
|
|
155
|
-
if characteristic.properties.contains(.notify) {
|
|
156
|
-
peripheral.setNotifyValue(true, for: characteristic)
|
|
125
|
+
func peripheral(_ device: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: (any Error)?) {
|
|
126
|
+
for trait in service.characteristics ?? [] {
|
|
127
|
+
if trait.properties.contains(.notify) {
|
|
128
|
+
device.setNotifyValue(true, for: trait)
|
|
157
129
|
}
|
|
158
|
-
if
|
|
159
|
-
|
|
130
|
+
if trait.properties.contains(.read) {
|
|
131
|
+
device.readValue(for: trait)
|
|
160
132
|
}
|
|
161
133
|
}
|
|
162
134
|
}
|
|
163
135
|
}
|
|
164
136
|
```
|
|
165
137
|
|
|
166
|
-
|
|
138
|
+
Check `properties` before each read, write or subscription.
|
|
167
139
|
|
|
168
|
-
|
|
140
|
+
## Reading, writing and notifications
|
|
141
|
+
|
|
142
|
+
Reads and notifications arrive in the same callback:
|
|
169
143
|
|
|
170
144
|
```swift
|
|
171
|
-
func peripheral(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
error: Error?
|
|
175
|
-
) {
|
|
176
|
-
guard let data = characteristic.value else { return }
|
|
177
|
-
|
|
178
|
-
switch characteristic.uuid {
|
|
145
|
+
func peripheral(_ device: CBPeripheral, didUpdateValueFor trait: CBCharacteristic, error: (any Error)?) {
|
|
146
|
+
guard error == nil, let bytes = trait.value else { return }
|
|
147
|
+
switch trait.uuid {
|
|
179
148
|
case CBUUID(string: "2A37"):
|
|
180
|
-
|
|
181
|
-
print("Heart rate: \(heartRate) bpm")
|
|
182
|
-
}
|
|
149
|
+
pulse = beatsPerMinute(bytes)
|
|
183
150
|
case CBUUID(string: "2A19"):
|
|
184
|
-
|
|
185
|
-
print("Battery: \(batteryLevel)%")
|
|
151
|
+
batteryPercent = bytes.first.map(Int.init)
|
|
186
152
|
default:
|
|
187
153
|
break
|
|
188
154
|
}
|
|
189
155
|
}
|
|
156
|
+
```
|
|
190
157
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
158
|
+
Heart Rate Measurement (`2A37`) needs care with its flags byte:
|
|
159
|
+
|
|
160
|
+
```swift
|
|
161
|
+
func beatsPerMinute(_ bytes: Data) -> Int? {
|
|
162
|
+
guard bytes.count >= 2 else { return nil }
|
|
163
|
+
let wide = bytes[bytes.startIndex] & 0x01 != 0
|
|
164
|
+
if wide {
|
|
165
|
+
guard bytes.count >= 3 else { return nil }
|
|
166
|
+
let low = UInt16(bytes[bytes.startIndex + 1])
|
|
167
|
+
let high = UInt16(bytes[bytes.startIndex + 2])
|
|
168
|
+
return Int(low | high << 8)
|
|
200
169
|
}
|
|
170
|
+
return Int(bytes[bytes.startIndex + 1])
|
|
201
171
|
}
|
|
202
172
|
```
|
|
203
173
|
|
|
204
|
-
|
|
174
|
+
Bit 0 set means a 16-bit little-endian value in bytes 1 and 2; otherwise the
|
|
175
|
+
value is the single byte 1.
|
|
176
|
+
|
|
177
|
+
### Choosing a write type
|
|
205
178
|
|
|
206
179
|
```swift
|
|
207
|
-
func
|
|
208
|
-
on peripheral: CBPeripheral,
|
|
209
|
-
preferResponse: Bool = true) {
|
|
180
|
+
func send(_ payload: Data, to trait: CBCharacteristic, on device: CBPeripheral, confirm: Bool) -> Bool {
|
|
210
181
|
let type: CBCharacteristicWriteType
|
|
211
|
-
if
|
|
182
|
+
if confirm, trait.properties.contains(.write) {
|
|
212
183
|
type = .withResponse
|
|
213
|
-
} else if
|
|
214
|
-
peripheral.canSendWriteWithoutResponse {
|
|
184
|
+
} else if trait.properties.contains(.writeWithoutResponse), device.canSendWriteWithoutResponse {
|
|
215
185
|
type = .withoutResponse
|
|
216
|
-
} else if
|
|
186
|
+
} else if trait.properties.contains(.write) {
|
|
217
187
|
type = .withResponse
|
|
218
188
|
} else {
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
guard data.count <= peripheral.maximumWriteValueLength(for: type) else { return }
|
|
223
|
-
peripheral.writeValue(data, for: characteristic, type: type)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Confirmation callback for .withResponse writes.
|
|
227
|
-
func peripheral(
|
|
228
|
-
_ peripheral: CBPeripheral,
|
|
229
|
-
didWriteValueFor characteristic: CBCharacteristic,
|
|
230
|
-
error: Error?
|
|
231
|
-
) {
|
|
232
|
-
if let error {
|
|
233
|
-
print("Write failed: \(error.localizedDescription)")
|
|
189
|
+
return false
|
|
234
190
|
}
|
|
191
|
+
guard payload.count <= device.maximumWriteValueLength(for: type) else { return false }
|
|
192
|
+
device.writeValue(payload, for: trait, type: type)
|
|
193
|
+
return true
|
|
235
194
|
}
|
|
236
|
-
|
|
237
|
-
// Resume queued .withoutResponse writes here.
|
|
238
|
-
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {}
|
|
239
195
|
```
|
|
240
196
|
|
|
241
|
-
|
|
197
|
+
- `.withResponse` writes are confirmed in `peripheral(_:didWriteValueFor:error:)`.
|
|
198
|
+
- When `canSendWriteWithoutResponse` is false, queue the data and resume in
|
|
199
|
+
`peripheralIsReady(toSendWriteWithoutResponse:)`.
|
|
200
|
+
- Payloads longer than `maximumWriteValueLength(for:)` must be split.
|
|
242
201
|
|
|
243
|
-
|
|
244
|
-
// Subscribe
|
|
245
|
-
peripheral.setNotifyValue(true, for: characteristic)
|
|
246
|
-
|
|
247
|
-
// Unsubscribe
|
|
248
|
-
peripheral.setNotifyValue(false, for: characteristic)
|
|
249
|
-
|
|
250
|
-
// Confirmation
|
|
251
|
-
func peripheral(
|
|
252
|
-
_ peripheral: CBPeripheral,
|
|
253
|
-
didUpdateNotificationStateFor characteristic: CBCharacteristic,
|
|
254
|
-
error: Error?
|
|
255
|
-
) {
|
|
256
|
-
if characteristic.isNotifying {
|
|
257
|
-
print("Now receiving notifications for \(characteristic.uuid)")
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
```
|
|
202
|
+
### Subscriptions
|
|
261
203
|
|
|
262
|
-
|
|
204
|
+
`setNotifyValue(true, for:)` subscribes and `setNotifyValue(false, for:)`
|
|
205
|
+
unsubscribes. The outcome arrives in
|
|
206
|
+
`peripheral(_:didUpdateNotificationStateFor:error:)`, where
|
|
207
|
+
`characteristic.isNotifying` tells you the current state.
|
|
263
208
|
|
|
264
|
-
|
|
209
|
+
## Peripheral role
|
|
265
210
|
|
|
266
211
|
```swift
|
|
267
|
-
final class
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
212
|
+
final class ThermometerBeacon: NSObject, CBPeripheralManagerDelegate {
|
|
213
|
+
static var service: CBUUID { CBUUID(string: "5E1A7D30-2C4B-4F55-9B0E-7A1C3D2E8F10") }
|
|
214
|
+
static var reading: CBUUID { CBUUID(string: "5E1A7D31-2C4B-4F55-9B0E-7A1C3D2E8F10") }
|
|
215
|
+
|
|
216
|
+
private var manager: CBPeripheralManager!
|
|
271
217
|
|
|
272
218
|
override init() {
|
|
273
219
|
super.init()
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
|
|
278
|
-
guard peripheral.state == .poweredOn else { return }
|
|
279
|
-
setupService()
|
|
220
|
+
manager = CBPeripheralManager(delegate: self, queue: nil)
|
|
280
221
|
}
|
|
281
222
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
223
|
+
func peripheralManagerDidUpdateState(_ manager: CBPeripheralManager) {
|
|
224
|
+
guard manager.state == .poweredOn else { return }
|
|
225
|
+
let reading = CBMutableCharacteristic(
|
|
226
|
+
type: Self.reading,
|
|
285
227
|
properties: [.read, .notify],
|
|
286
228
|
value: nil,
|
|
287
229
|
permissions: [.readable]
|
|
288
230
|
)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
service
|
|
292
|
-
peripheralManager.add(service)
|
|
231
|
+
let service = CBMutableService(type: Self.service, primary: true)
|
|
232
|
+
service.characteristics = [reading]
|
|
233
|
+
manager.add(service)
|
|
293
234
|
}
|
|
294
235
|
|
|
295
|
-
func peripheralManager(
|
|
296
|
-
_ peripheral: CBPeripheralManager,
|
|
297
|
-
didAdd service: CBService,
|
|
298
|
-
error: Error?
|
|
299
|
-
) {
|
|
236
|
+
func peripheralManager(_ manager: CBPeripheralManager, didAdd service: CBService, error: (any Error)?) {
|
|
300
237
|
guard error == nil else { return }
|
|
301
|
-
|
|
302
|
-
CBAdvertisementDataServiceUUIDsKey: [
|
|
303
|
-
CBAdvertisementDataLocalNameKey: "
|
|
238
|
+
manager.startAdvertising([
|
|
239
|
+
CBAdvertisementDataServiceUUIDsKey: [Self.service],
|
|
240
|
+
CBAdvertisementDataLocalNameKey: "Porch Thermometer",
|
|
304
241
|
])
|
|
305
242
|
}
|
|
306
243
|
}
|
|
307
244
|
```
|
|
308
245
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
246
|
+
Custom services use full 128-bit UUIDs created with `CBUUID(string:)`. Leaving
|
|
247
|
+
`value` nil makes the characteristic dynamic, so reads reach your delegate.
|
|
248
|
+
Answering read, write and subscription requests is shown in the
|
|
249
|
+
[patterns reference](references/ble-patterns.md).
|
|
312
250
|
|
|
313
|
-
|
|
251
|
+
## Background operation
|
|
314
252
|
|
|
315
|
-
|
|
316
|
-
- Scan options, including `CBCentralManagerScanOptionAllowDuplicatesKey`, have no effect
|
|
253
|
+
**Central (`bluetooth-central`):**
|
|
317
254
|
|
|
318
|
-
|
|
255
|
+
- Background scans must name service UUIDs; a `nil` scan runs only in the
|
|
256
|
+
foreground.
|
|
257
|
+
- Scan options, `CBCentralManagerScanOptionAllowDuplicatesKey` included, are
|
|
258
|
+
ignored in the background.
|
|
319
259
|
|
|
320
|
-
|
|
260
|
+
**Peripheral (`bluetooth-peripheral`):**
|
|
321
261
|
|
|
322
|
-
- Without this mode, published
|
|
323
|
-
|
|
324
|
-
-
|
|
262
|
+
- Without this mode, the contents of published services are unavailable while
|
|
263
|
+
the app is suspended.
|
|
264
|
+
- In the background the local name is not advertised.
|
|
265
|
+
- Service UUIDs move to an overflow area that only centrals scanning
|
|
266
|
+
explicitly for those services can see.
|
|
325
267
|
|
|
326
|
-
## State
|
|
268
|
+
## State restoration
|
|
327
269
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
### Central Manager State Restoration
|
|
270
|
+
If the app is terminated, the system can relaunch it for a Bluetooth event and
|
|
271
|
+
hand back the manager it had. Opt in with a restoration identifier:
|
|
332
272
|
|
|
333
273
|
```swift
|
|
334
|
-
|
|
335
|
-
centralManager = CBCentralManager(
|
|
274
|
+
central = CBCentralManager(
|
|
336
275
|
delegate: self,
|
|
337
276
|
queue: nil,
|
|
338
|
-
options: [CBCentralManagerOptionRestoreIdentifierKey: "
|
|
277
|
+
options: [CBCentralManagerOptionRestoreIdentifierKey: "scale-central"]
|
|
339
278
|
)
|
|
340
279
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
|
|
347
|
-
as? [CBPeripheral] {
|
|
348
|
-
for peripheral in peripherals {
|
|
349
|
-
// Re-assign delegate and retain
|
|
350
|
-
peripheral.delegate = self
|
|
351
|
-
discoveredPeripheral = peripheral
|
|
280
|
+
func centralManager(_ central: CBCentralManager, willRestoreState state: [String: Any]) {
|
|
281
|
+
if let devices = state[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
|
|
282
|
+
for device in devices {
|
|
283
|
+
device.delegate = self
|
|
284
|
+
scale = device
|
|
352
285
|
}
|
|
353
286
|
}
|
|
354
|
-
let
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
as? [String: Any]
|
|
358
|
-
// Resume scanning with restoredServices/restoredOptions if still needed.
|
|
359
|
-
}
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### Peripheral Manager State Restoration
|
|
363
|
-
|
|
364
|
-
```swift
|
|
365
|
-
peripheralManager = CBPeripheralManager(
|
|
366
|
-
delegate: self,
|
|
367
|
-
queue: nil,
|
|
368
|
-
options: [CBPeripheralManagerOptionRestoreIdentifierKey: "myPeripheral"]
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
func peripheralManager(
|
|
372
|
-
_ peripheral: CBPeripheralManager,
|
|
373
|
-
willRestoreState dict: [String: Any]
|
|
374
|
-
) {
|
|
375
|
-
let services = dict[CBPeripheralManagerRestoredStateServicesKey]
|
|
376
|
-
as? [CBMutableService]
|
|
377
|
-
let advertisement = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey]
|
|
378
|
-
as? [String: Any]
|
|
379
|
-
// Reconnect app state to restored services/advertisement as needed.
|
|
380
|
-
}
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
## Common Mistakes
|
|
384
|
-
|
|
385
|
-
### DON'T: Scan or connect before poweredOn
|
|
386
|
-
|
|
387
|
-
```swift
|
|
388
|
-
// WRONG: Scanning immediately -- manager may not be ready
|
|
389
|
-
let manager = CBCentralManager(delegate: self, queue: nil)
|
|
390
|
-
manager.scanForPeripherals(withServices: nil) // May silently fail
|
|
391
|
-
|
|
392
|
-
// CORRECT: Wait for poweredOn in the delegate
|
|
393
|
-
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
394
|
-
if central.state == .poweredOn {
|
|
395
|
-
central.scanForPeripherals(withServices: [serviceUUID])
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
### DON'T: Lose the peripheral reference
|
|
401
|
-
|
|
402
|
-
Core Bluetooth does not retain discovered peripherals. If you don't hold a
|
|
403
|
-
strong reference, the peripheral is deallocated and the connection fails silently.
|
|
404
|
-
|
|
405
|
-
```swift
|
|
406
|
-
// WRONG: No strong reference kept
|
|
407
|
-
func centralManager(_ central: CBCentralManager,
|
|
408
|
-
didDiscover peripheral: CBPeripheral, ...) {
|
|
409
|
-
central.connect(peripheral) // peripheral may be deallocated
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// CORRECT: Retain the peripheral
|
|
413
|
-
func centralManager(_ central: CBCentralManager,
|
|
414
|
-
didDiscover peripheral: CBPeripheral, ...) {
|
|
415
|
-
self.discoveredPeripheral = peripheral // Strong reference
|
|
416
|
-
central.connect(peripheral)
|
|
417
|
-
}
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### DON'T: Scan for nil services in production
|
|
421
|
-
|
|
422
|
-
```swift
|
|
423
|
-
// WRONG: Discovers every BLE device in range -- drains battery
|
|
424
|
-
centralManager.scanForPeripherals(withServices: nil)
|
|
425
|
-
|
|
426
|
-
// CORRECT: Specify the service UUIDs you need
|
|
427
|
-
centralManager.scanForPeripherals(withServices: [targetServiceUUID])
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
### DON'T: Assume connection order or timing
|
|
431
|
-
|
|
432
|
-
```swift
|
|
433
|
-
// WRONG: Assuming immediate connection
|
|
434
|
-
centralManager.connect(peripheral)
|
|
435
|
-
discoverServicesNow() // Peripheral not connected yet
|
|
436
|
-
|
|
437
|
-
// CORRECT: Discover services in the didConnect callback
|
|
438
|
-
func centralManager(_ central: CBCentralManager,
|
|
439
|
-
didConnect peripheral: CBPeripheral) {
|
|
440
|
-
peripheral.delegate = self
|
|
441
|
-
peripheral.discoverServices([serviceUUID])
|
|
442
|
-
}
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### DON'T: Write without checking properties and flow control
|
|
446
|
-
|
|
447
|
-
```swift
|
|
448
|
-
// WRONG: May fail, report an error, or provide no confirmation
|
|
449
|
-
peripheral.writeValue(data, for: characteristic, type: .withResponse)
|
|
450
|
-
|
|
451
|
-
// CORRECT: Check properties, length, and .withoutResponse flow control
|
|
452
|
-
if characteristic.properties.contains(.write),
|
|
453
|
-
data.count <= peripheral.maximumWriteValueLength(for: .withResponse) {
|
|
454
|
-
peripheral.writeValue(data, for: characteristic, type: .withResponse)
|
|
455
|
-
} else if characteristic.properties.contains(.writeWithoutResponse),
|
|
456
|
-
peripheral.canSendWriteWithoutResponse,
|
|
457
|
-
data.count <= peripheral.maximumWriteValueLength(for: .withoutResponse) {
|
|
458
|
-
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
|
|
287
|
+
let services = state[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID]
|
|
288
|
+
let options = state[CBCentralManagerRestoredStateScanOptionsKey] as? [String: Any]
|
|
289
|
+
resumeScanIfNeeded(services: services, options: options)
|
|
459
290
|
}
|
|
460
291
|
```
|
|
461
292
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
-
|
|
471
|
-
|
|
472
|
-
-
|
|
473
|
-
|
|
474
|
-
-
|
|
475
|
-
-
|
|
476
|
-
|
|
477
|
-
-
|
|
293
|
+
A peripheral manager does the same with
|
|
294
|
+
`CBPeripheralManagerOptionRestoreIdentifierKey`, and its
|
|
295
|
+
`peripheralManager(_:willRestoreState:)` reads
|
|
296
|
+
`CBPeripheralManagerRestoredStateServicesKey` (an array of `CBMutableService`)
|
|
297
|
+
and `CBPeripheralManagerRestoredStateAdvertisementDataKey`.
|
|
298
|
+
|
|
299
|
+
## Common mistakes
|
|
300
|
+
|
|
301
|
+
- **Acting before `.poweredOn`.** Scans and connects can fail without a word.
|
|
302
|
+
Start them from the state callback.
|
|
303
|
+
- **Dropping the peripheral.** Nothing else retains it, and the connection
|
|
304
|
+
quietly never happens.
|
|
305
|
+
- **`nil` service scans in production.** Heavy on battery; list the UUIDs.
|
|
306
|
+
- **Assuming the connection is instant.** Start service discovery in
|
|
307
|
+
`didConnect`, not right after `connect`.
|
|
308
|
+
- **Writing blind.** Ignoring properties, the maximum write length or
|
|
309
|
+
`.withoutResponse` flow control leads to failed or unconfirmed writes.
|
|
310
|
+
|
|
311
|
+
## Review checklist
|
|
312
|
+
|
|
313
|
+
- [ ] `NSBluetoothAlwaysUsageDescription` is present
|
|
314
|
+
- [ ] Every operation waits for `.poweredOn`
|
|
315
|
+
- [ ] Discovered peripherals are held strongly
|
|
316
|
+
- [ ] Production scans list specific service UUIDs
|
|
317
|
+
- [ ] `CBPeripheralDelegate` is set before `discoverServices`
|
|
318
|
+
- [ ] Properties are checked before read, write or notify
|
|
319
|
+
- [ ] Writes fit within `maximumWriteValueLength(for:)`
|
|
320
|
+
- [ ] `.withoutResponse` writes respect `canSendWriteWithoutResponse`
|
|
321
|
+
- [ ] Needed background modes are declared
|
|
322
|
+
- [ ] A restoration identifier is set when relaunch on BLE events matters
|
|
323
|
+
- [ ] `willRestoreState` is implemented whenever restoration is on
|
|
324
|
+
- [ ] Scanning stops once the target is found
|
|
325
|
+
- [ ] Disconnects are handled, with reconnection where it makes sense
|
|
326
|
+
- [ ] Write type matches the characteristic's properties
|
|
478
327
|
|
|
479
328
|
## References
|
|
480
329
|
|
|
481
|
-
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
330
|
+
- [BLE patterns](references/ble-patterns.md): SwiftUI manager and view,
|
|
331
|
+
reconnection, byte parsing, write flow control, several peripherals, L2CAP,
|
|
332
|
+
answering requests as a peripheral
|
|
333
|
+
- Apple documentation (developer.apple.com):
|
|
334
|
+
[framework](https://developer.apple.com/documentation/corebluetooth),
|
|
335
|
+
[central manager](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager),
|
|
336
|
+
[peripheral](https://developer.apple.com/documentation/corebluetooth/cbperipheral),
|
|
337
|
+
[peripheral manager](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager),
|
|
338
|
+
[service](https://developer.apple.com/documentation/corebluetooth/cbservice),
|
|
339
|
+
[characteristic](https://developer.apple.com/documentation/corebluetooth/cbcharacteristic),
|
|
340
|
+
[UUID](https://developer.apple.com/documentation/corebluetooth/cbuuid),
|
|
341
|
+
[central delegate](https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate),
|
|
342
|
+
[peripheral delegate](https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate),
|
|
343
|
+
[usage description key](https://developer.apple.com/documentation/bundleresources/information-property-list/nsbluetoothalwaysusagedescription),
|
|
344
|
+
[authorization values](https://developer.apple.com/documentation/corebluetooth/cbmanagerauthorization),
|
|
345
|
+
[scanning](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/scanforperipherals(withservices:options:)),
|
|
346
|
+
[advertising](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/startadvertising(_:)),
|
|
347
|
+
[writing values](https://developer.apple.com/documentation/corebluetooth/cbperipheral) (`writeValue(_:for:type:)` on the peripheral page),
|
|
348
|
+
[maximum write length](https://developer.apple.com/documentation/corebluetooth/cbperipheral/maximumwritevaluelength(for:)),
|
|
349
|
+
[write-without-response readiness](https://developer.apple.com/documentation/corebluetooth/cbperipheral/cansendwritewithoutresponse),
|
|
350
|
+
[background execution modes](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)
|