@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,436 +1,336 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Motion patterns
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Companion material for the core-motion skill. Each section is a self-contained
|
|
4
|
+
pattern that goes beyond the basics in [../SKILL.md](../SKILL.md).
|
|
5
5
|
|
|
6
6
|
## Contents
|
|
7
7
|
|
|
8
|
-
- [
|
|
9
|
-
- [CMBatchedSensorManager
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [Water
|
|
8
|
+
- [A shared @Observable motion service for SwiftUI](#a-shared-observable-motion-service-for-swiftui)
|
|
9
|
+
- [CMBatchedSensorManager for high-rate capture](#cmbatchedsensormanager-for-high-rate-capture)
|
|
10
|
+
- [Head tracking with CMHeadphoneMotionManager](#head-tracking-with-cmheadphonemotionmanager)
|
|
11
|
+
- [A step dashboard](#a-step-dashboard)
|
|
12
|
+
- [Switching app modes from activity](#switching-app-modes-from-activity)
|
|
13
|
+
- [Water submersion on watchOS](#water-submersion-on-watchos)
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## A shared @Observable motion service for SwiftUI
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Keep the single `CMMotionManager` inside one main-actor service. Views observe
|
|
18
|
+
its published angles and never touch the manager directly.
|
|
18
19
|
|
|
19
20
|
```swift
|
|
20
21
|
import CoreMotion
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
@Observable
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
var
|
|
31
|
-
var
|
|
32
|
-
var
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
func
|
|
37
|
-
guard
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self.userAcceleration = motion.userAcceleration
|
|
22
|
+
import Observation
|
|
23
|
+
|
|
24
|
+
@Observable @MainActor
|
|
25
|
+
final class TiltService {
|
|
26
|
+
static let shared = TiltService()
|
|
27
|
+
|
|
28
|
+
private let hardware = CMMotionManager()
|
|
29
|
+
private(set) var pitch = 0.0
|
|
30
|
+
private(set) var roll = 0.0
|
|
31
|
+
private(set) var yaw = 0.0
|
|
32
|
+
private(set) var push = CMAcceleration()
|
|
33
|
+
private(set) var isRunning = false
|
|
34
|
+
|
|
35
|
+
private init() {}
|
|
36
|
+
|
|
37
|
+
func begin(every interval: TimeInterval = 1.0 / 30.0) {
|
|
38
|
+
guard hardware.isDeviceMotionAvailable, !isRunning else { return }
|
|
39
|
+
hardware.deviceMotionUpdateInterval = interval
|
|
40
|
+
hardware.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: .main) { [weak self] sample, _ in
|
|
41
|
+
guard let sample else { return }
|
|
42
|
+
MainActor.assumeIsolated { // the handler runs on OperationQueue.main
|
|
43
|
+
guard let self else { return }
|
|
44
|
+
self.pitch = sample.attitude.pitch
|
|
45
|
+
self.roll = sample.attitude.roll
|
|
46
|
+
self.yaw = sample.attitude.yaw
|
|
47
|
+
self.push = sample.userAcceleration
|
|
48
|
+
}
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
isRunning = true
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
func
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
func end() {
|
|
54
|
+
hardware.stopDeviceMotionUpdates()
|
|
55
|
+
isRunning = false
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
A bubble level built on it. The surface counts as level while both pitch and
|
|
61
|
+
roll stay under 0.05 radians; 30 Hz is enough for smooth movement.
|
|
61
62
|
|
|
62
63
|
```swift
|
|
63
|
-
|
|
64
|
-
@State private var motionService = MotionService.shared
|
|
65
|
-
|
|
66
|
-
var body: some View {
|
|
67
|
-
VStack {
|
|
68
|
-
Circle()
|
|
69
|
-
.fill(.blue)
|
|
70
|
-
.frame(width: 60, height: 60)
|
|
71
|
-
.offset(
|
|
72
|
-
x: motionService.roll * 100,
|
|
73
|
-
y: motionService.pitch * 100
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
Text("Roll: \(motionService.roll, format: .number.precision(.fractionLength(2)))")
|
|
77
|
-
Text("Pitch: \(motionService.pitch, format: .number.precision(.fractionLength(2)))")
|
|
78
|
-
}
|
|
79
|
-
.onAppear { motionService.startDeviceMotion() }
|
|
80
|
-
.onDisappear { motionService.stop() }
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Level Indicator
|
|
64
|
+
import SwiftUI
|
|
86
65
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
66
|
+
struct BubbleLevel: View {
|
|
67
|
+
private let tilt = TiltService.shared
|
|
68
|
+
private let tolerance = 0.05
|
|
69
|
+
private static let angle = FloatingPointFormatStyle<Double>.number.precision(.fractionLength(2))
|
|
90
70
|
|
|
91
|
-
private var
|
|
92
|
-
abs(
|
|
71
|
+
private var isFlat: Bool {
|
|
72
|
+
abs(tilt.pitch) < tolerance && abs(tilt.roll) < tolerance
|
|
93
73
|
}
|
|
94
74
|
|
|
95
75
|
var body: some View {
|
|
96
|
-
|
|
76
|
+
VStack(spacing: 24) {
|
|
97
77
|
Circle()
|
|
98
|
-
.
|
|
99
|
-
.frame(width:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.frame(width: 20, height: 20)
|
|
104
|
-
.offset(
|
|
105
|
-
x: motionService.roll * 100,
|
|
106
|
-
y: motionService.pitch * -100
|
|
107
|
-
)
|
|
78
|
+
.fill(isFlat ? .green : .orange)
|
|
79
|
+
.frame(width: 44, height: 44)
|
|
80
|
+
.offset(x: tilt.roll * 120, y: tilt.pitch * 120)
|
|
81
|
+
Text("Pitch \(tilt.pitch.formatted(Self.angle))")
|
|
82
|
+
Text("Roll \(tilt.roll.formatted(Self.angle))")
|
|
108
83
|
}
|
|
109
|
-
.onAppear {
|
|
110
|
-
.onDisappear {
|
|
84
|
+
.onAppear { tilt.begin(every: 1.0 / 30.0) }
|
|
85
|
+
.onDisappear { tilt.end() }
|
|
111
86
|
}
|
|
112
87
|
}
|
|
113
88
|
```
|
|
114
89
|
|
|
115
|
-
## CMBatchedSensorManager
|
|
116
|
-
|
|
117
|
-
`CMBatchedSensorManager` delivers batches of high-frequency accelerometer and
|
|
118
|
-
device-motion data for workout-style motion analysis, such as golf swings or bat
|
|
119
|
-
swings. The async update sequences are watchOS 10+ APIs; check availability and
|
|
120
|
-
authorization before starting updates.
|
|
90
|
+
## CMBatchedSensorManager for high-rate capture
|
|
121
91
|
|
|
122
|
-
|
|
92
|
+
`CMBatchedSensorManager` hands over accelerometer and device-motion samples in
|
|
93
|
+
batches at a high rate, which suits swing and stroke analysis. The async
|
|
94
|
+
update sequences need watchOS 10 or later. Before starting, confirm
|
|
95
|
+
`CMBatchedSensorManager.isAccelerometerSupported` and that
|
|
96
|
+
`authorizationStatus` is neither `.denied` nor `.restricted`.
|
|
123
97
|
|
|
124
98
|
```swift
|
|
125
99
|
import CoreMotion
|
|
126
100
|
|
|
127
|
-
|
|
101
|
+
#if os(watchOS)
|
|
102
|
+
@available(watchOS 10.0, *)
|
|
128
103
|
@MainActor
|
|
129
|
-
final class
|
|
130
|
-
private let
|
|
131
|
-
private var
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Update UI with most recent
|
|
149
|
-
if let latest = batch.last {
|
|
150
|
-
latestAcceleration = latest.acceleration
|
|
104
|
+
final class SwingRecorder {
|
|
105
|
+
private let batcher = CMBatchedSensorManager()
|
|
106
|
+
private var reader: Task<Void, Never>?
|
|
107
|
+
private(set) var lastSample: CMAccelerometerData?
|
|
108
|
+
|
|
109
|
+
func record() {
|
|
110
|
+
guard CMBatchedSensorManager.isAccelerometerSupported else { return }
|
|
111
|
+
switch CMBatchedSensorManager.authorizationStatus {
|
|
112
|
+
case .denied, .restricted: return
|
|
113
|
+
default: break
|
|
114
|
+
}
|
|
115
|
+
reader = Task { [weak self, batcher] in
|
|
116
|
+
do {
|
|
117
|
+
for try await chunk in batcher.accelerometerUpdates() {
|
|
118
|
+
if Task.isCancelled { break }
|
|
119
|
+
for sample in chunk {
|
|
120
|
+
_ = (sample.acceleration, sample.timestamp) // feed the analyzer
|
|
121
|
+
}
|
|
122
|
+
self?.lastSample = chunk.last
|
|
151
123
|
}
|
|
124
|
+
} catch {
|
|
125
|
+
// surface the stream failure to the UI
|
|
152
126
|
}
|
|
153
127
|
}
|
|
154
128
|
}
|
|
155
129
|
|
|
156
|
-
func
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
130
|
+
func finish() {
|
|
131
|
+
reader?.cancel()
|
|
132
|
+
reader = nil
|
|
133
|
+
batcher.stopAccelerometerUpdates()
|
|
160
134
|
}
|
|
161
135
|
}
|
|
136
|
+
#endif
|
|
162
137
|
```
|
|
163
138
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// Start updates, then read the frequency the device reports.
|
|
170
|
-
batchedManager.startAccelerometerUpdates()
|
|
171
|
-
let reportedHz = batchedManager.accelerometerDataFrequency
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
`accelerometerDataFrequency` and `deviceMotionDataFrequency` are read-only. Do
|
|
175
|
-
not assign them; use the reported values to size buffers, throttle UI updates, or
|
|
176
|
-
downsample processed results.
|
|
139
|
+
To learn the delivered rate, start the stream (for example with
|
|
140
|
+
`startAccelerometerUpdates()`) and then read `accelerometerDataFrequency`;
|
|
141
|
+
`deviceMotionDataFrequency` is the device-motion counterpart. Both are
|
|
142
|
+
read-only. Use the numbers to size ring buffers, throttle what the UI redraws,
|
|
143
|
+
or pick a downsampling factor.
|
|
177
144
|
|
|
178
|
-
##
|
|
145
|
+
## Head tracking with CMHeadphoneMotionManager
|
|
179
146
|
|
|
180
|
-
|
|
181
|
-
On iOS and macOS
|
|
182
|
-
|
|
183
|
-
session.
|
|
147
|
+
`CMHeadphoneMotionManager` reports head orientation from supported AirPods,
|
|
148
|
+
such as AirPods Pro and AirPods Max. On iOS and macOS the app needs
|
|
149
|
+
`NSMotionUsageDescription`.
|
|
184
150
|
|
|
185
151
|
```swift
|
|
186
152
|
import CoreMotion
|
|
187
153
|
|
|
188
|
-
@Observable
|
|
189
154
|
@MainActor
|
|
190
|
-
final class
|
|
191
|
-
private let
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
headphoneManager.startConnectionStatusUpdates()
|
|
202
|
-
headphoneManager.startDeviceMotionUpdates(to: .main) { [weak self] motion, error in
|
|
203
|
-
guard let self, let motion else { return }
|
|
204
|
-
self.headPitch = motion.attitude.pitch
|
|
205
|
-
self.headYaw = motion.attitude.yaw
|
|
155
|
+
final class HeadTracker: NSObject, CMHeadphoneMotionManagerDelegate {
|
|
156
|
+
private let earbuds = CMHeadphoneMotionManager()
|
|
157
|
+
private(set) var isConnected = false
|
|
158
|
+
|
|
159
|
+
func begin() {
|
|
160
|
+
guard earbuds.isDeviceMotionAvailable else { return }
|
|
161
|
+
earbuds.delegate = self
|
|
162
|
+
earbuds.startConnectionStatusUpdates()
|
|
163
|
+
earbuds.startDeviceMotionUpdates(to: .main) { motion, error in
|
|
164
|
+
guard let pose = motion?.attitude, error == nil else { return }
|
|
165
|
+
_ = (pose.pitch, pose.yaw) // nod and turn
|
|
206
166
|
}
|
|
207
167
|
}
|
|
208
168
|
|
|
209
|
-
func
|
|
210
|
-
|
|
211
|
-
|
|
169
|
+
func end() {
|
|
170
|
+
earbuds.stopDeviceMotionUpdates()
|
|
171
|
+
earbuds.stopConnectionStatusUpdates()
|
|
212
172
|
}
|
|
213
|
-
}
|
|
214
173
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
_ manager: CMHeadphoneMotionManager
|
|
218
|
-
) {
|
|
219
|
-
Task { @MainActor in isConnected = true }
|
|
174
|
+
nonisolated func headphoneMotionManagerDidConnect(_ source: CMHeadphoneMotionManager) {
|
|
175
|
+
Task { @MainActor in self.isConnected = true }
|
|
220
176
|
}
|
|
221
177
|
|
|
222
|
-
nonisolated func headphoneMotionManagerDidDisconnect(
|
|
223
|
-
|
|
224
|
-
) {
|
|
225
|
-
Task { @MainActor in isConnected = false }
|
|
178
|
+
nonisolated func headphoneMotionManagerDidDisconnect(_ source: CMHeadphoneMotionManager) {
|
|
179
|
+
Task { @MainActor in self.isConnected = false }
|
|
226
180
|
}
|
|
227
181
|
}
|
|
228
182
|
```
|
|
229
183
|
|
|
230
|
-
|
|
184
|
+
`startConnectionStatusUpdates()` delivers connect and disconnect callbacks even
|
|
185
|
+
while no motion session is running. Stop both the motion stream and the
|
|
186
|
+
connection updates when finished.
|
|
231
187
|
|
|
232
|
-
|
|
188
|
+
## A step dashboard
|
|
233
189
|
|
|
234
190
|
```swift
|
|
235
191
|
import CoreMotion
|
|
192
|
+
import Observation
|
|
236
193
|
import SwiftUI
|
|
237
194
|
|
|
238
|
-
@Observable
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
private
|
|
242
|
-
|
|
243
|
-
var
|
|
244
|
-
var todayDistance: Double = 0
|
|
245
|
-
var floorsAscended: Int = 0
|
|
195
|
+
@Observable @MainActor
|
|
196
|
+
final class StepTally {
|
|
197
|
+
private let counter = CMPedometer()
|
|
198
|
+
private(set) var steps = 0
|
|
199
|
+
private(set) var meters = 0.0
|
|
200
|
+
private(set) var floorsUp = 0
|
|
246
201
|
|
|
247
|
-
func
|
|
202
|
+
func begin() {
|
|
248
203
|
guard CMPedometer.isStepCountingAvailable() else { return }
|
|
204
|
+
let midnight = Calendar.current.startOfDay(for: .now)
|
|
249
205
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
guard let self, let data else { return }
|
|
254
|
-
Task { @MainActor in
|
|
255
|
-
self.todaySteps = data.numberOfSteps.intValue
|
|
256
|
-
self.todayDistance = data.distance?.doubleValue ?? 0
|
|
257
|
-
self.floorsAscended = data.floorsAscended?.intValue ?? 0
|
|
258
|
-
}
|
|
206
|
+
counter.queryPedometerData(from: midnight, to: .now) { data, _ in
|
|
207
|
+
guard let data else { return }
|
|
208
|
+
Task { @MainActor in self.apply(data) }
|
|
259
209
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
guard CMPedometer.isStepCountingAvailable() else { return }
|
|
264
|
-
|
|
265
|
-
let startOfDay = Calendar.current.startOfDay(for: Date())
|
|
266
|
-
|
|
267
|
-
pedometer.startUpdates(from: startOfDay) { [weak self] data, error in
|
|
268
|
-
guard let self, let data else { return }
|
|
269
|
-
Task { @MainActor in
|
|
270
|
-
self.todaySteps = data.numberOfSteps.intValue
|
|
271
|
-
self.todayDistance = data.distance?.doubleValue ?? 0
|
|
272
|
-
self.floorsAscended = data.floorsAscended?.intValue ?? 0
|
|
273
|
-
}
|
|
210
|
+
counter.startUpdates(from: midnight) { data, _ in
|
|
211
|
+
guard let data else { return }
|
|
212
|
+
Task { @MainActor in self.apply(data) }
|
|
274
213
|
}
|
|
275
214
|
}
|
|
276
215
|
|
|
277
|
-
func
|
|
278
|
-
|
|
216
|
+
func end() { counter.stopUpdates() }
|
|
217
|
+
|
|
218
|
+
private func apply(_ data: CMPedometerData) {
|
|
219
|
+
steps = data.numberOfSteps.intValue
|
|
220
|
+
meters = data.distance?.doubleValue ?? meters
|
|
221
|
+
floorsUp = data.floorsAscended?.intValue ?? floorsUp
|
|
279
222
|
}
|
|
280
223
|
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### SwiftUI Dashboard View
|
|
284
224
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
@State private var pedometerService = PedometerService()
|
|
225
|
+
struct DailyStepsView: View {
|
|
226
|
+
@State private var tally = StepTally()
|
|
288
227
|
|
|
289
228
|
var body: some View {
|
|
290
229
|
List {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
format: .measurement(width: .abbreviated)
|
|
299
|
-
)
|
|
300
|
-
}
|
|
301
|
-
if CMPedometer.isFloorCountingAvailable() {
|
|
302
|
-
LabeledContent("Floors Climbed") {
|
|
303
|
-
Text("\(pedometerService.floorsAscended)")
|
|
304
|
-
}
|
|
305
|
-
}
|
|
230
|
+
LabeledContent("Steps", value: tally.steps, format: .number)
|
|
231
|
+
LabeledContent("Distance") {
|
|
232
|
+
Text(Measurement(value: tally.meters, unit: UnitLength.meters),
|
|
233
|
+
format: .measurement(width: .abbreviated))
|
|
234
|
+
}
|
|
235
|
+
if CMPedometer.isFloorCountingAvailable() {
|
|
236
|
+
LabeledContent("Floors climbed", value: tally.floorsUp, format: .number)
|
|
306
237
|
}
|
|
307
238
|
}
|
|
308
|
-
.onAppear {
|
|
309
|
-
.onDisappear {
|
|
239
|
+
.onAppear { tally.begin() }
|
|
240
|
+
.onDisappear { tally.end() }
|
|
310
241
|
}
|
|
311
242
|
}
|
|
312
243
|
```
|
|
313
244
|
|
|
314
|
-
##
|
|
245
|
+
## Switching app modes from activity
|
|
315
246
|
|
|
316
|
-
|
|
247
|
+
A navigation-style app can change its presentation when the user starts
|
|
248
|
+
driving or cycling. Ignore low-confidence readings, check the flags in
|
|
249
|
+
priority order, and keep the current mode while stationary so a red light
|
|
250
|
+
does not flip a driver back to walking.
|
|
317
251
|
|
|
318
252
|
```swift
|
|
319
253
|
import CoreMotion
|
|
320
254
|
|
|
321
|
-
|
|
322
|
-
@MainActor
|
|
323
|
-
final class NavigationModeService {
|
|
324
|
-
private let activityManager = CMMotionActivityManager()
|
|
325
|
-
|
|
326
|
-
enum Mode: String {
|
|
327
|
-
case walking, driving, cycling, unknown
|
|
328
|
-
}
|
|
255
|
+
enum TravelMode { case walking, cycling, driving }
|
|
329
256
|
|
|
330
|
-
|
|
257
|
+
@MainActor
|
|
258
|
+
final class TravelModeDetector {
|
|
259
|
+
private let classifier = CMMotionActivityManager()
|
|
260
|
+
private(set) var mode: TravelMode = .walking
|
|
331
261
|
|
|
332
|
-
func
|
|
262
|
+
func begin() {
|
|
333
263
|
guard CMMotionActivityManager.isActivityAvailable() else { return }
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if
|
|
341
|
-
self.
|
|
342
|
-
} else if
|
|
343
|
-
self.
|
|
344
|
-
} else if activity.walking || activity.running {
|
|
345
|
-
self.currentMode = .walking
|
|
346
|
-
} else if activity.stationary {
|
|
347
|
-
// Keep previous mode when stationary (e.g., at a stoplight)
|
|
264
|
+
classifier.startActivityUpdates(to: .main) { [weak self] update in
|
|
265
|
+
guard let update, update.confidence != .low else { return }
|
|
266
|
+
MainActor.assumeIsolated {
|
|
267
|
+
guard let self else { return }
|
|
268
|
+
if update.automotive {
|
|
269
|
+
self.mode = .driving
|
|
270
|
+
} else if update.cycling {
|
|
271
|
+
self.mode = .cycling
|
|
272
|
+
} else if update.running || update.walking {
|
|
273
|
+
self.mode = .walking
|
|
348
274
|
}
|
|
275
|
+
// stationary: leave mode unchanged
|
|
349
276
|
}
|
|
350
277
|
}
|
|
351
278
|
}
|
|
352
279
|
|
|
353
|
-
func
|
|
354
|
-
activityManager.stopActivityUpdates()
|
|
355
|
-
}
|
|
280
|
+
func end() { classifier.stopActivityUpdates() }
|
|
356
281
|
}
|
|
357
282
|
```
|
|
358
283
|
|
|
359
|
-
## Water
|
|
284
|
+
## Water submersion on watchOS
|
|
360
285
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
286
|
+
Decide support with `CMWaterSubmersionManager.waterSubmersionAvailable`, never
|
|
287
|
+
with a model list. Apple Watch Ultra supports submersion, and Apple Watch
|
|
288
|
+
Series 10 supports Shallow Depth and Pressure, so do not describe the feature
|
|
289
|
+
as Ultra-only.
|
|
365
290
|
|
|
366
|
-
Setup
|
|
291
|
+
Setup:
|
|
367
292
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
293
|
+
1. Add `NSMotionUsageDescription`.
|
|
294
|
+
2. Choose the capability. Dives no deeper than 6 m are covered by the
|
|
295
|
+
capability named Shallow Depth and Pressure. Deeper dives, to 40 m, need the Submerged Depth and Pressure
|
|
296
|
+
entitlement, which requires an application to Apple.
|
|
297
|
+
3. Add `underwater-depth` to `WKBackgroundModes`. That keeps the app frontmost
|
|
298
|
+
during a dive and makes it eligible to launch automatically on submersion.
|
|
299
|
+
|
|
300
|
+
Check availability before creating the manager, keep a strong reference to
|
|
301
|
+
it, and assign its `delegate`.
|
|
375
302
|
|
|
376
303
|
```swift
|
|
377
304
|
import CoreMotion
|
|
378
305
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
final class DiveService: NSObject {
|
|
382
|
-
private var submersionManager: CMWaterSubmersionManager?
|
|
383
|
-
|
|
384
|
-
var isSubmerged = false
|
|
385
|
-
var currentDepth: Double?
|
|
386
|
-
var waterTemperature: Double?
|
|
306
|
+
final class DiveMonitor: NSObject, CMWaterSubmersionManagerDelegate {
|
|
307
|
+
private var sensor: CMWaterSubmersionManager?
|
|
387
308
|
|
|
388
|
-
func
|
|
309
|
+
func begin() {
|
|
389
310
|
guard CMWaterSubmersionManager.waterSubmersionAvailable else { return }
|
|
390
311
|
let manager = CMWaterSubmersionManager()
|
|
391
312
|
manager.delegate = self
|
|
392
|
-
|
|
313
|
+
sensor = manager
|
|
393
314
|
}
|
|
394
|
-
}
|
|
395
315
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
_
|
|
399
|
-
didUpdate event: CMWaterSubmersionEvent
|
|
400
|
-
) {
|
|
401
|
-
Task { @MainActor in
|
|
402
|
-
isSubmerged = event.state == .submerged
|
|
403
|
-
}
|
|
316
|
+
func manager(_ source: CMWaterSubmersionManager, didUpdate event: CMWaterSubmersionEvent) {
|
|
317
|
+
let underwater = event.state == .submerged
|
|
318
|
+
_ = underwater
|
|
404
319
|
}
|
|
405
320
|
|
|
406
|
-
|
|
407
|
-
_
|
|
408
|
-
didUpdate measurement: CMWaterSubmersionMeasurement
|
|
409
|
-
) {
|
|
410
|
-
Task { @MainActor in
|
|
411
|
-
currentDepth = measurement.depth?.value
|
|
412
|
-
}
|
|
321
|
+
func manager(_ source: CMWaterSubmersionManager, didUpdate measurement: CMWaterSubmersionMeasurement) {
|
|
322
|
+
if let depth = measurement.depth { _ = depth.value } // meters
|
|
413
323
|
}
|
|
414
324
|
|
|
415
|
-
|
|
416
|
-
_
|
|
417
|
-
didUpdate temperature: CMWaterTemperature
|
|
418
|
-
) {
|
|
419
|
-
Task { @MainActor in
|
|
420
|
-
waterTemperature = temperature.temperature.value
|
|
421
|
-
}
|
|
325
|
+
func manager(_ source: CMWaterSubmersionManager, didUpdate measurement: CMWaterTemperature) {
|
|
326
|
+
_ = measurement.temperature.value
|
|
422
327
|
}
|
|
423
328
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
errorOccurred error: any Error
|
|
427
|
-
) {
|
|
428
|
-
print("Submersion error: \(error)")
|
|
329
|
+
func manager(_ source: CMWaterSubmersionManager, errorOccurred error: any Error) {
|
|
330
|
+
// CMError.notEntitled arrives here when the capability is missing
|
|
429
331
|
}
|
|
430
332
|
}
|
|
431
333
|
```
|
|
432
334
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
entitlement. If the app lacks the entitlement, the delegate receives
|
|
436
|
-
`CMError.notEntitled` and no submersion data.
|
|
335
|
+
An app that lacks the capability or entitlement gets `CMError.notEntitled` in
|
|
336
|
+
`manager(_:errorOccurred:)` and no depth data.
|