@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,498 +1,348 @@
|
|
|
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
|
+
- `manager.authorization`: `.denied` and `.restricted` stay that way until the
|
|
37
|
+
user changes Settings, so show guidance rather than retrying.
|
|
38
|
+
- `manager.state`: do nothing (scan, connect, advertise, add services) until it
|
|
39
|
+
is `.poweredOn`.
|
|
51
40
|
|
|
52
|
-
|
|
41
|
+
## Central role
|
|
53
42
|
|
|
54
|
-
|
|
43
|
+
### Scanning
|
|
55
44
|
|
|
56
45
|
```swift
|
|
57
46
|
import CoreBluetooth
|
|
58
47
|
|
|
59
|
-
final class
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
final class ScaleLink: NSObject, CBCentralManagerDelegate {
|
|
49
|
+
static var weightService: CBUUID { CBUUID(string: "181D") }
|
|
50
|
+
|
|
51
|
+
private var central: CBCentralManager!
|
|
52
|
+
private var scale: CBPeripheral?
|
|
62
53
|
|
|
63
54
|
override init() {
|
|
64
55
|
super.init()
|
|
65
|
-
|
|
56
|
+
central = CBCentralManager(delegate: self, queue: nil)
|
|
66
57
|
}
|
|
67
58
|
|
|
68
59
|
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
69
60
|
guard central.state == .poweredOn else { return }
|
|
70
|
-
|
|
61
|
+
central.scanForPeripherals(
|
|
62
|
+
withServices: [Self.weightService],
|
|
63
|
+
options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func centralManager(_ central: CBCentralManager, didDiscover found: CBPeripheral,
|
|
68
|
+
advertisementData: [String: Any], rssi: NSNumber) {
|
|
69
|
+
guard rssi.intValue > -75 else { return }
|
|
70
|
+
scale = found
|
|
71
|
+
central.stopScan()
|
|
72
|
+
central.connect(found)
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
- Scan for the service UUIDs you care about. `withServices: nil` finds
|
|
78
|
+
everything, burns battery and has no place in a shipping app.
|
|
79
|
+
- Ignoring weak signals (here anything at or below -75 dBm) is optional.
|
|
80
|
+
- Keep a strong reference to the discovered `CBPeripheral`. Core Bluetooth
|
|
81
|
+
does not, and a released peripheral silently never connects.
|
|
82
|
+
- Stop scanning as soon as the target turns up, then connect.
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
The implicitly unwrapped `central` exists only because `self` must be fully
|
|
85
|
+
initialized before it can be the delegate. `CBUUID` is not `Sendable`, and
|
|
86
|
+
Swift 6 rejects a stored `static let` of it on a type without actor isolation,
|
|
87
|
+
so the UUIDs are computed static properties. A `@MainActor` type can keep
|
|
88
|
+
stored ones, as the SwiftUI monitor in the reference does.
|
|
79
89
|
|
|
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
|
|
90
|
+
### Connecting and disconnecting
|
|
106
91
|
|
|
107
92
|
```swift
|
|
108
|
-
|
|
109
|
-
_ central: CBCentralManager,
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
peripheral.discoverServices([heartRateServiceUUID])
|
|
114
|
-
}
|
|
93
|
+
extension ScaleLink {
|
|
94
|
+
func centralManager(_ central: CBCentralManager, didConnect device: CBPeripheral) {
|
|
95
|
+
device.delegate = self
|
|
96
|
+
device.discoverServices([Self.weightService])
|
|
97
|
+
}
|
|
115
98
|
|
|
116
|
-
func centralManager(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
isReconnecting: Bool,
|
|
121
|
-
error: Error?
|
|
122
|
-
) {
|
|
123
|
-
if isReconnecting {
|
|
124
|
-
// System is automatically reconnecting
|
|
125
|
-
return
|
|
99
|
+
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral device: CBPeripheral,
|
|
100
|
+
timestamp: CFAbsoluteTime, isReconnecting: Bool, error: (any Error)?) {
|
|
101
|
+
if isReconnecting { return }
|
|
102
|
+
scale = nil
|
|
126
103
|
}
|
|
127
|
-
// Handle disconnection -- optionally reconnect
|
|
128
|
-
discoveredPeripheral = nil
|
|
129
104
|
}
|
|
130
105
|
```
|
|
131
106
|
|
|
132
|
-
|
|
107
|
+
Set the peripheral's delegate before asking it for anything. When
|
|
108
|
+
`isReconnecting` is true the system is already reconnecting on your behalf;
|
|
109
|
+
otherwise decide whether to reconnect yourself and release the reference.
|
|
133
110
|
|
|
134
|
-
|
|
111
|
+
## Discovering services and characteristics
|
|
135
112
|
|
|
136
113
|
```swift
|
|
137
|
-
extension
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
for service in services {
|
|
144
|
-
peripheral.discoverCharacteristics(nil, for: service)
|
|
114
|
+
extension ScaleLink: CBPeripheralDelegate {
|
|
115
|
+
static var weightMeasurement: CBUUID { CBUUID(string: "2A9D") }
|
|
116
|
+
|
|
117
|
+
func peripheral(_ device: CBPeripheral, didDiscoverServices error: (any Error)?) {
|
|
118
|
+
for service in device.services ?? [] {
|
|
119
|
+
device.discoverCharacteristics([Self.weightMeasurement, CBUUID(string: "2A19")], for: service)
|
|
145
120
|
}
|
|
146
121
|
}
|
|
147
122
|
|
|
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)
|
|
123
|
+
func peripheral(_ device: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: (any Error)?) {
|
|
124
|
+
for trait in service.characteristics ?? [] {
|
|
125
|
+
if trait.properties.contains(.notify) {
|
|
126
|
+
device.setNotifyValue(true, for: trait)
|
|
157
127
|
}
|
|
158
|
-
if
|
|
159
|
-
|
|
128
|
+
if trait.properties.contains(.read) {
|
|
129
|
+
device.readValue(for: trait)
|
|
160
130
|
}
|
|
161
131
|
}
|
|
162
132
|
}
|
|
163
133
|
}
|
|
164
134
|
```
|
|
165
135
|
|
|
166
|
-
|
|
136
|
+
Check `properties` before each read, write or subscription.
|
|
167
137
|
|
|
168
|
-
|
|
138
|
+
## Reading, writing and notifications
|
|
139
|
+
|
|
140
|
+
Reads and notifications arrive in the same callback:
|
|
169
141
|
|
|
170
142
|
```swift
|
|
171
|
-
func peripheral(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
error: Error?
|
|
175
|
-
) {
|
|
176
|
-
guard let data = characteristic.value else { return }
|
|
177
|
-
|
|
178
|
-
switch characteristic.uuid {
|
|
143
|
+
func peripheral(_ device: CBPeripheral, didUpdateValueFor trait: CBCharacteristic, error: (any Error)?) {
|
|
144
|
+
guard error == nil, let bytes = trait.value else { return }
|
|
145
|
+
switch trait.uuid {
|
|
179
146
|
case CBUUID(string: "2A37"):
|
|
180
|
-
|
|
181
|
-
print("Heart rate: \(heartRate) bpm")
|
|
182
|
-
}
|
|
147
|
+
pulse = beatsPerMinute(bytes)
|
|
183
148
|
case CBUUID(string: "2A19"):
|
|
184
|
-
|
|
185
|
-
print("Battery: \(batteryLevel)%")
|
|
149
|
+
batteryPercent = bytes.first.map(Int.init)
|
|
186
150
|
default:
|
|
187
151
|
break
|
|
188
152
|
}
|
|
189
153
|
}
|
|
154
|
+
```
|
|
190
155
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
156
|
+
Heart Rate Measurement (`2A37`) needs care with its flags byte:
|
|
157
|
+
|
|
158
|
+
```swift
|
|
159
|
+
func beatsPerMinute(_ bytes: Data) -> Int? {
|
|
160
|
+
guard bytes.count >= 2 else { return nil }
|
|
161
|
+
let wide = bytes[bytes.startIndex] & 0x01 != 0
|
|
162
|
+
if wide {
|
|
163
|
+
guard bytes.count >= 3 else { return nil }
|
|
164
|
+
let low = UInt16(bytes[bytes.startIndex + 1])
|
|
165
|
+
let high = UInt16(bytes[bytes.startIndex + 2])
|
|
166
|
+
return Int(low | high << 8)
|
|
200
167
|
}
|
|
168
|
+
return Int(bytes[bytes.startIndex + 1])
|
|
201
169
|
}
|
|
202
170
|
```
|
|
203
171
|
|
|
204
|
-
|
|
172
|
+
Bit 0 set means a 16-bit little-endian value in bytes 1 and 2; otherwise the
|
|
173
|
+
value is the single byte 1.
|
|
174
|
+
|
|
175
|
+
### Choosing a write type
|
|
205
176
|
|
|
206
177
|
```swift
|
|
207
|
-
func
|
|
208
|
-
on peripheral: CBPeripheral,
|
|
209
|
-
preferResponse: Bool = true) {
|
|
178
|
+
func send(_ payload: Data, to trait: CBCharacteristic, on device: CBPeripheral, confirm: Bool) -> Bool {
|
|
210
179
|
let type: CBCharacteristicWriteType
|
|
211
|
-
if
|
|
180
|
+
if confirm, trait.properties.contains(.write) {
|
|
212
181
|
type = .withResponse
|
|
213
|
-
} else if
|
|
214
|
-
peripheral.canSendWriteWithoutResponse {
|
|
182
|
+
} else if trait.properties.contains(.writeWithoutResponse), device.canSendWriteWithoutResponse {
|
|
215
183
|
type = .withoutResponse
|
|
216
|
-
} else if
|
|
184
|
+
} else if trait.properties.contains(.write) {
|
|
217
185
|
type = .withResponse
|
|
218
186
|
} 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)")
|
|
187
|
+
return false
|
|
234
188
|
}
|
|
189
|
+
guard payload.count <= device.maximumWriteValueLength(for: type) else { return false }
|
|
190
|
+
device.writeValue(payload, for: trait, type: type)
|
|
191
|
+
return true
|
|
235
192
|
}
|
|
236
|
-
|
|
237
|
-
// Resume queued .withoutResponse writes here.
|
|
238
|
-
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {}
|
|
239
193
|
```
|
|
240
194
|
|
|
241
|
-
|
|
195
|
+
- `.withResponse` writes are confirmed in `peripheral(_:didWriteValueFor:error:)`.
|
|
196
|
+
- When `canSendWriteWithoutResponse` is false, queue the data and resume in
|
|
197
|
+
`peripheralIsReady(toSendWriteWithoutResponse:)`.
|
|
198
|
+
- Payloads longer than `maximumWriteValueLength(for:)` must be split.
|
|
242
199
|
|
|
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
|
-
```
|
|
200
|
+
### Subscriptions
|
|
261
201
|
|
|
262
|
-
|
|
202
|
+
`setNotifyValue(true, for:)` subscribes and `setNotifyValue(false, for:)`
|
|
203
|
+
unsubscribes. The outcome arrives in
|
|
204
|
+
`peripheral(_:didUpdateNotificationStateFor:error:)`, where
|
|
205
|
+
`characteristic.isNotifying` tells you the current state.
|
|
263
206
|
|
|
264
|
-
|
|
207
|
+
## Peripheral role
|
|
265
208
|
|
|
266
209
|
```swift
|
|
267
|
-
final class
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
210
|
+
final class ThermometerBeacon: NSObject, CBPeripheralManagerDelegate {
|
|
211
|
+
static var service: CBUUID { CBUUID(string: "5E1A7D30-2C4B-4F55-9B0E-7A1C3D2E8F10") }
|
|
212
|
+
static var reading: CBUUID { CBUUID(string: "5E1A7D31-2C4B-4F55-9B0E-7A1C3D2E8F10") }
|
|
213
|
+
|
|
214
|
+
private var manager: CBPeripheralManager!
|
|
271
215
|
|
|
272
216
|
override init() {
|
|
273
217
|
super.init()
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
|
|
278
|
-
guard peripheral.state == .poweredOn else { return }
|
|
279
|
-
setupService()
|
|
218
|
+
manager = CBPeripheralManager(delegate: self, queue: nil)
|
|
280
219
|
}
|
|
281
220
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
221
|
+
func peripheralManagerDidUpdateState(_ manager: CBPeripheralManager) {
|
|
222
|
+
guard manager.state == .poweredOn else { return }
|
|
223
|
+
let reading = CBMutableCharacteristic(
|
|
224
|
+
type: Self.reading,
|
|
285
225
|
properties: [.read, .notify],
|
|
286
226
|
value: nil,
|
|
287
227
|
permissions: [.readable]
|
|
288
228
|
)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
service
|
|
292
|
-
peripheralManager.add(service)
|
|
229
|
+
let service = CBMutableService(type: Self.service, primary: true)
|
|
230
|
+
service.characteristics = [reading]
|
|
231
|
+
manager.add(service)
|
|
293
232
|
}
|
|
294
233
|
|
|
295
|
-
func peripheralManager(
|
|
296
|
-
_ peripheral: CBPeripheralManager,
|
|
297
|
-
didAdd service: CBService,
|
|
298
|
-
error: Error?
|
|
299
|
-
) {
|
|
234
|
+
func peripheralManager(_ manager: CBPeripheralManager, didAdd service: CBService, error: (any Error)?) {
|
|
300
235
|
guard error == nil else { return }
|
|
301
|
-
|
|
302
|
-
CBAdvertisementDataServiceUUIDsKey: [
|
|
303
|
-
CBAdvertisementDataLocalNameKey: "
|
|
236
|
+
manager.startAdvertising([
|
|
237
|
+
CBAdvertisementDataServiceUUIDsKey: [Self.service],
|
|
238
|
+
CBAdvertisementDataLocalNameKey: "Porch Thermometer",
|
|
304
239
|
])
|
|
305
240
|
}
|
|
306
241
|
}
|
|
307
242
|
```
|
|
308
243
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
244
|
+
Custom services use full 128-bit UUIDs created with `CBUUID(string:)`. Leaving
|
|
245
|
+
`value` nil makes the characteristic dynamic, so reads reach your delegate.
|
|
246
|
+
Answering read, write and subscription requests is shown in the
|
|
247
|
+
[patterns reference](references/ble-patterns.md).
|
|
312
248
|
|
|
313
|
-
|
|
249
|
+
## Background operation
|
|
314
250
|
|
|
315
|
-
|
|
316
|
-
- Scan options, including `CBCentralManagerScanOptionAllowDuplicatesKey`, have no effect
|
|
251
|
+
**Central (`bluetooth-central`):**
|
|
317
252
|
|
|
318
|
-
|
|
253
|
+
- Background scans must name service UUIDs; a `nil` scan runs only in the
|
|
254
|
+
foreground.
|
|
255
|
+
- Scan options, `CBCentralManagerScanOptionAllowDuplicatesKey` included, are
|
|
256
|
+
ignored in the background.
|
|
319
257
|
|
|
320
|
-
|
|
258
|
+
**Peripheral (`bluetooth-peripheral`):**
|
|
321
259
|
|
|
322
|
-
- Without this mode, published
|
|
323
|
-
|
|
324
|
-
-
|
|
260
|
+
- Without this mode, the contents of published services are unavailable while
|
|
261
|
+
the app is suspended.
|
|
262
|
+
- In the background the local name is not advertised.
|
|
263
|
+
- Service UUIDs move to an overflow area that only centrals scanning
|
|
264
|
+
explicitly for those services can see.
|
|
325
265
|
|
|
326
|
-
## State
|
|
266
|
+
## State restoration
|
|
327
267
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
### Central Manager State Restoration
|
|
268
|
+
If the app is terminated, the system can relaunch it for a Bluetooth event and
|
|
269
|
+
hand back the manager it had. Opt in with a restoration identifier:
|
|
332
270
|
|
|
333
271
|
```swift
|
|
334
|
-
|
|
335
|
-
centralManager = CBCentralManager(
|
|
272
|
+
central = CBCentralManager(
|
|
336
273
|
delegate: self,
|
|
337
274
|
queue: nil,
|
|
338
|
-
options: [CBCentralManagerOptionRestoreIdentifierKey: "
|
|
275
|
+
options: [CBCentralManagerOptionRestoreIdentifierKey: "scale-central"]
|
|
339
276
|
)
|
|
340
277
|
|
|
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
|
|
278
|
+
func centralManager(_ central: CBCentralManager, willRestoreState state: [String: Any]) {
|
|
279
|
+
if let devices = state[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
|
|
280
|
+
for device in devices {
|
|
281
|
+
device.delegate = self
|
|
282
|
+
scale = device
|
|
352
283
|
}
|
|
353
284
|
}
|
|
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)
|
|
285
|
+
let services = state[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID]
|
|
286
|
+
let options = state[CBCentralManagerRestoredStateScanOptionsKey] as? [String: Any]
|
|
287
|
+
resumeScanIfNeeded(services: services, options: options)
|
|
459
288
|
}
|
|
460
289
|
```
|
|
461
290
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
-
|
|
471
|
-
|
|
472
|
-
-
|
|
473
|
-
|
|
474
|
-
-
|
|
475
|
-
-
|
|
476
|
-
|
|
477
|
-
-
|
|
291
|
+
A peripheral manager does the same with
|
|
292
|
+
`CBPeripheralManagerOptionRestoreIdentifierKey`, and its
|
|
293
|
+
`peripheralManager(_:willRestoreState:)` reads
|
|
294
|
+
`CBPeripheralManagerRestoredStateServicesKey` (an array of `CBMutableService`)
|
|
295
|
+
and `CBPeripheralManagerRestoredStateAdvertisementDataKey`.
|
|
296
|
+
|
|
297
|
+
## Common mistakes
|
|
298
|
+
|
|
299
|
+
- **Acting before `.poweredOn`.** Scans and connects can fail without a word.
|
|
300
|
+
Start them from the state callback.
|
|
301
|
+
- **Dropping the peripheral.** Nothing else retains it, and the connection
|
|
302
|
+
quietly never happens.
|
|
303
|
+
- **`nil` service scans in production.** Heavy on battery; list the UUIDs.
|
|
304
|
+
- **Assuming the connection is instant.** Start service discovery in
|
|
305
|
+
`didConnect`, not right after `connect`.
|
|
306
|
+
- **Writing blind.** Ignoring properties, the maximum write length or
|
|
307
|
+
`.withoutResponse` flow control leads to failed or unconfirmed writes.
|
|
308
|
+
|
|
309
|
+
## Review checklist
|
|
310
|
+
|
|
311
|
+
- [ ] `NSBluetoothAlwaysUsageDescription` is present
|
|
312
|
+
- [ ] Every operation waits for `.poweredOn`
|
|
313
|
+
- [ ] Discovered peripherals are held strongly
|
|
314
|
+
- [ ] Production scans list specific service UUIDs
|
|
315
|
+
- [ ] `CBPeripheralDelegate` is set before `discoverServices`
|
|
316
|
+
- [ ] Properties are checked before read, write or notify
|
|
317
|
+
- [ ] Writes fit within `maximumWriteValueLength(for:)`
|
|
318
|
+
- [ ] `.withoutResponse` writes respect `canSendWriteWithoutResponse`
|
|
319
|
+
- [ ] Needed background modes are declared
|
|
320
|
+
- [ ] A restoration identifier is set when relaunch on BLE events matters
|
|
321
|
+
- [ ] `willRestoreState` is implemented whenever restoration is on
|
|
322
|
+
- [ ] Scanning stops once the target is found
|
|
323
|
+
- [ ] Disconnects are handled, with reconnection where it makes sense
|
|
324
|
+
- [ ] Write type matches the characteristic's properties
|
|
478
325
|
|
|
479
326
|
## References
|
|
480
327
|
|
|
481
|
-
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
328
|
+
- [BLE patterns](references/ble-patterns.md): SwiftUI manager and view,
|
|
329
|
+
reconnection, byte parsing, write flow control, several peripherals, L2CAP,
|
|
330
|
+
answering requests as a peripheral
|
|
331
|
+
- Apple documentation (developer.apple.com):
|
|
332
|
+
[framework](https://developer.apple.com/documentation/corebluetooth),
|
|
333
|
+
[central manager](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager),
|
|
334
|
+
[peripheral](https://developer.apple.com/documentation/corebluetooth/cbperipheral),
|
|
335
|
+
[peripheral manager](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager),
|
|
336
|
+
[service](https://developer.apple.com/documentation/corebluetooth/cbservice),
|
|
337
|
+
[characteristic](https://developer.apple.com/documentation/corebluetooth/cbcharacteristic),
|
|
338
|
+
[UUID](https://developer.apple.com/documentation/corebluetooth/cbuuid),
|
|
339
|
+
[central delegate](https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate),
|
|
340
|
+
[peripheral delegate](https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate),
|
|
341
|
+
[usage description key](https://developer.apple.com/documentation/bundleresources/information-property-list/nsbluetoothalwaysusagedescription),
|
|
342
|
+
[authorization values](https://developer.apple.com/documentation/corebluetooth/cbmanagerauthorization),
|
|
343
|
+
[scanning](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/scanforperipherals(withservices:options:)),
|
|
344
|
+
[advertising](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/startadvertising(_:)),
|
|
345
|
+
[writing values](https://developer.apple.com/documentation/corebluetooth/cbperipheral) (`writeValue(_:for:type:)` on the peripheral page),
|
|
346
|
+
[maximum write length](https://developer.apple.com/documentation/corebluetooth/cbperipheral/maximumwritevaluelength(for:)),
|
|
347
|
+
[write-without-response readiness](https://developer.apple.com/documentation/corebluetooth/cbperipheral/cansendwritewithoutresponse),
|
|
348
|
+
[background execution modes](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)
|