
Core Motion
- 2.6k installs
- 944 repo stars
- Updated July 15, 2026
- dpearson2699/swift-ios-skills
core-motion is an iOS skill for accelerometer, gyroscope, device motion, pedometer, and activity recognition APIs.
About
The core-motion skill documents CoreMotion sensor APIs for fitness, navigation, and motion-driven interactions on iOS and watchOS targeting Swift 6.3 and iOS 26 plus. Setup requires NSMotionUsageDescription in Info.plist because missing keys crash on first access. Use one CMMotionManager per app; multiple instances degrade update rates. Accelerometer and gyroscope sections show interval configuration, main-queue handlers, and a polling pattern for games via display link reads. Device motion fuses sensors into CMDeviceMotion with attitude, userAcceleration, gravity, and heading, selecting attitude reference frames from availableAttitudeReferenceFrames with fallbacks when magnetic or true north frames need location. CMPedometer covers historical queries and live step, distance, floor, pace, and cadence updates with availability checks. CMMotionActivityManager detects walking, running, cycling, automotive, and stationary states with confidence levels plus historical queryActivityStarting. CMAltimeter, headphone motion, batched workout motion, and submersion depth are listed in the description for specialized flows. Battery guidance ties update intervals to power impact.
- Requires NSMotionUsageDescription or the app crashes on motion access.
- Use exactly one CMMotionManager instance per application.
- Device motion picks an available attitude reference frame at runtime.
- CMPedometer supports historical queries and live step or distance updates.
- CMMotionActivityManager classifies walking, running, driving, and cycling.
Core Motion by the numbers
- 2,609 all-time installs (skills.sh)
- +114 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #77 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
core-motion capabilities & compatibility
- Capabilities
- accelerometer and gyroscope streaming or polling · device motion attitude frames and heading access · pedometer historical and live updates with avail · activity recognition live and historical queries · plist, authorization, and battery interval guida
- Use cases
- frontend · ui design
- Platforms
- macOS
What core-motion says it does
Without this key, the app crashes on first access.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill core-motionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.6k |
|---|---|
| repo stars | ★ 944 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 15, 2026 |
| Repository | dpearson2699/swift-ios-skills ↗ |
How do I read motion sensors, steps, or activity states correctly in a Swift iOS app?
Read accelerometer, gyroscope, device motion, pedometer, activity, altitude, and related CoreMotion data on iOS and watchOS.
Who is it for?
iOS apps using motion sensors, step counts, activity detection, or tilt-based controls.
Skip if: Skip for Android sensor APIs or server-side motion analytics without on-device CoreMotion.
When should I use this skill?
User mentions CoreMotion, CMPedometer, CMMotionActivityManager, or device motion attitude.
What you get
Configured CoreMotion managers with proper plist keys, intervals, authorization checks, and stop cleanup.
- SwiftUI motion service
- CMMotionManager lifecycle implementation
Files
CoreMotion
Read device sensor data -- accelerometer, gyroscope, magnetometer, pedometer, activity recognition, altitude, headphone motion, batched motion, and submersion depth -- on iOS and watchOS. CoreMotion fuses raw sensor inputs into processed device-motion data and provides pedometer/activity APIs for fitness and navigation use cases. Targets Swift 6.3 / iOS 26+.
Contents
- Setup
- CMMotionManager: Sensor Data
- Processed Device Motion
- CMPedometer: Step and Distance Data
- CMMotionActivityManager: Activity Recognition
- CMAltimeter: Altitude Data
- Update Intervals and Battery
- Common Mistakes
- Review Checklist
- References
Setup
Info.plist
Add NSMotionUsageDescription to Info.plist with a user-facing string explaining why your app needs motion data. Without this key, the app crashes on first access.
<key>NSMotionUsageDescription</key>
<string>This app uses motion data to track your activity.</string>Authorization
Use the matching manager's authorizationStatus() or authorizationStatus property when an API exposes one (CMPedometer, CMMotionActivityManager, CMAltimeter, headphone motion, batched sensors, and submersion). Raw CMMotionManager accelerometer/gyro/device-motion streams have no explicit authorization request API; still ship the usage string and handle errors from start/update callbacks.
import CoreMotion
let status = CMMotionActivityManager.authorizationStatus()
switch status {
case .notDetermined:
// Will prompt on first use
break
case .authorized:
break
case .restricted, .denied:
// Direct user to Settings
break
@unknown default:
break
}CMMotionManager: Sensor Data
Create exactly one CMMotionManager per app. Multiple instances degrade sensor update rates.
import CoreMotion
let motionManager = CMMotionManager()Accelerometer Updates
guard motionManager.isAccelerometerAvailable else { return }
motionManager.accelerometerUpdateInterval = 1.0 / 60.0 // 60 Hz
motionManager.startAccelerometerUpdates(to: .main) { data, error in
guard let acceleration = data?.acceleration else { return }
print("x: \(acceleration.x), y: \(acceleration.y), z: \(acceleration.z)")
}
// When done:
motionManager.stopAccelerometerUpdates()Gyroscope Updates
guard motionManager.isGyroAvailable else { return }
motionManager.gyroUpdateInterval = 1.0 / 60.0
motionManager.startGyroUpdates(to: .main) { data, error in
guard let rotationRate = data?.rotationRate else { return }
print("x: \(rotationRate.x), y: \(rotationRate.y), z: \(rotationRate.z)")
}
motionManager.stopGyroUpdates()Polling Pattern (Games)
For games, start updates without a handler and poll the latest sample each frame:
motionManager.startAccelerometerUpdates()
// In your game loop / display link:
if let data = motionManager.accelerometerData {
let tilt = data.acceleration.x
// Move player based on tilt
}Processed Device Motion
Device motion fuses accelerometer, gyroscope, and magnetometer into a single CMDeviceMotion object with attitude, user acceleration (gravity removed), rotation rate, and calibrated magnetic field.
When giving device-motion guidance, show the runtime frame check in the snippet instead of hard-coding a corrected, magnetic-north, or true-north frame. Fall back to .xArbitraryZVertical when the preferred frame is unavailable.
guard motionManager.isDeviceMotionAvailable else { return }
let availableFrames = CMMotionManager.availableAttitudeReferenceFrames()
let frame: CMAttitudeReferenceFrame = availableFrames.contains(.xArbitraryCorrectedZVertical)
? .xArbitraryCorrectedZVertical
: .xArbitraryZVertical
motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
motionManager.startDeviceMotionUpdates(
using: frame,
to: .main
) { motion, error in
guard let motion else { return }
let attitude = motion.attitude // roll, pitch, yaw
let userAccel = motion.userAcceleration
let gravity = motion.gravity
let heading = motion.heading // degrees relative to the current frame
print("Pitch: \(attitude.pitch), Roll: \(attitude.roll)")
}
motionManager.stopDeviceMotionUpdates()Attitude Reference Frames
For simple tilt controls, use .xArbitraryZVertical or .xArbitraryCorrectedZVertical; they avoid magnetometer/location dependencies. Before requesting corrected, magnetic-north, or true-north frames, call CMMotionManager.availableAttitudeReferenceFrames() and fall back to an available frame.
| Frame | Use Case |
|---|---|
.xArbitraryZVertical | Default. Z is vertical, X arbitrary at start. Most games. |
.xArbitraryCorrectedZVertical | Same as above, corrected for gyro drift over time. |
.xMagneticNorthZVertical | X points to magnetic north. Requires magnetometer. |
.xTrueNorthZVertical | X points to true north. Requires magnetometer + location. |
Check available frames before use:
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
// Safe to use true north
}CMPedometer: Step and Distance Data
CMPedometer provides step counts, distance, pace, cadence, and floor counts.
let pedometer = CMPedometer()
guard CMPedometer.isStepCountingAvailable() else { return }
// Historical query
pedometer.queryPedometerData(
from: Calendar.current.startOfDay(for: Date()),
to: Date()
) { data, error in
guard let data else { return }
print("Steps today: \(data.numberOfSteps)")
print("Distance: \(data.distance?.doubleValue ?? 0) meters")
print("Floors up: \(data.floorsAscended?.intValue ?? 0)")
}
// Live updates
pedometer.startUpdates(from: Date()) { data, error in
guard let data else { return }
print("Steps: \(data.numberOfSteps)")
}
// Stop when done
pedometer.stopUpdates()Availability Checks
| Method | What It Checks |
|---|---|
isStepCountingAvailable() | Step counter hardware |
isDistanceAvailable() | Distance estimation |
isFloorCountingAvailable() | Barometric altimeter for floors |
isPaceAvailable() | Pace data |
isCadenceAvailable() | Cadence data |
CMMotionActivityManager: Activity Recognition
Detects whether the user is stationary, walking, running, cycling, or in a vehicle.
let activityManager = CMMotionActivityManager()
guard CMMotionActivityManager.isActivityAvailable() else { return }
// Live activity updates
activityManager.startActivityUpdates(to: .main) { activity in
guard let activity else { return }
if activity.walking {
print("Walking (confidence: \(activity.confidence.rawValue))")
} else if activity.running {
print("Running")
} else if activity.automotive {
print("In vehicle")
} else if activity.cycling {
print("Cycling")
} else if activity.stationary {
print("Stationary")
}
}
activityManager.stopActivityUpdates()Historical Activity Query
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
activityManager.queryActivityStarting(
from: yesterday,
to: Date(),
to: .main
) { activities, error in
guard let activities else { return }
for activity in activities {
print("\(activity.startDate): walking=\(activity.walking)")
}
}CMAltimeter: Altitude Data
Altimeter access is covered by NSMotionUsageDescription; handle denied motion access through unavailable data and update-handler errors.
let altimeter = CMAltimeter()
guard CMAltimeter.isRelativeAltitudeAvailable() else { return }
altimeter.startRelativeAltitudeUpdates(to: .main) { data, error in
guard let data else { return }
print("Relative altitude: \(data.relativeAltitude) meters")
print("Pressure: \(data.pressure) kPa")
}
altimeter.stopRelativeAltitudeUpdates()Absolute altitude is altitude relative to sea level, not GPS-based altitude. First check availability. Absolute altitude is available only on supported hardware such as iPhone 12 or later and Apple Watch Series 6, Apple Watch SE, or later.
guard CMAltimeter.isAbsoluteAltitudeAvailable() else { return }
altimeter.startAbsoluteAltitudeUpdates(to: .main) { data, error in
guard let data else { return }
print("Altitude: \(data.altitude)m, accuracy: \(data.accuracy)m")
}
altimeter.stopAbsoluteAltitudeUpdates()Update Intervals and Battery
| Interval | Hz | Use Case | Battery Impact |
|---|---|---|---|
1.0 / 10.0 | 10 | UI orientation | Low |
1.0 / 30.0 | 30 | Casual games | Moderate |
1.0 / 60.0 | 60 | Action games | High |
1.0 / 100.0 | 100 | Max rate (iPhone) | Very High |
Use the lowest frequency that meets your needs. Do not assume a fixed maximum sample rate across devices. For high-frequency workout motion, use CMBatchedSensorManager where supported and read its reported accelerometerDataFrequency or deviceMotionDataFrequency instead of assigning those read-only properties.
Common Mistakes
DON'T: Create multiple CMMotionManager instances
// WRONG -- degrades update rates for all instances
class ViewA { let motion = CMMotionManager() }
class ViewB { let motion = CMMotionManager() }
// CORRECT -- single instance, shared across the app
@Observable
final class MotionService {
static let shared = MotionService()
let manager = CMMotionManager()
}DON'T: Skip sensor availability checks
// WRONG -- crashes on devices without gyroscope
motionManager.startGyroUpdates(to: .main) { data, _ in }
// CORRECT -- check first
guard motionManager.isGyroAvailable else {
showUnsupportedMessage()
return
}
motionManager.startGyroUpdates(to: .main) { data, _ in }DON'T: Forget to stop updates
// WRONG -- updates keep running, draining battery
class MotionVC: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
motionManager.startAccelerometerUpdates(to: .main) { _, _ in }
}
// Missing viewDidDisappear stop!
}
// CORRECT -- stop in the counterpart lifecycle method
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
motionManager.stopAccelerometerUpdates()
}DON'T: Use unnecessarily high update rates
// WRONG -- 100 Hz for a compass display
motionManager.deviceMotionUpdateInterval = 1.0 / 100.0
// CORRECT -- 10 Hz is more than enough for a compass
motionManager.deviceMotionUpdateInterval = 1.0 / 10.0DON'T: Assume all CMMotionActivity properties are mutually exclusive
// WRONG -- checking only one property
if activity.walking { handleWalking() }
// CORRECT -- multiple can be true simultaneously; check confidence
if activity.walking && activity.confidence == .high {
handleWalking()
} else if activity.automotive && activity.confidence != .low {
handleDriving()
}Review Checklist
- [ ]
NSMotionUsageDescriptionpresent in Info.plist with a clear explanation - [ ] Single
CMMotionManagerinstance shared across the app - [ ] Sensor availability checked before starting updates (
isAccelerometerAvailable, etc.) - [ ] Authorization status checked before pedometer/activity APIs
- [ ] Update interval set to the lowest acceptable frequency
- [ ] All
start*Updatescalls have matchingstop*Updatesin lifecycle counterparts - [ ] Handlers dispatched to appropriate queues (not blocking main for heavy processing)
- [ ]
CMMotionActivity.confidencechecked before acting on activity type - [ ] Error parameters checked in update handlers
- [ ] Device-motion snippets call
CMMotionManager.availableAttitudeReferenceFrames()before requesting a specific attitude frame - [ ] Attitude reference frame chosen based on actual need (not defaulting to true north unnecessarily)
References
- Extended patterns (SwiftUI integration, batched sensor manager, headphone motion, water submersion): references/motion-patterns.md
- CoreMotion framework
- CMMotionManager
- CMPedometer
- CMMotionActivityManager
- CMDeviceMotion
- CMAltimeter
- CMAbsoluteAltitudeData
- CMBatchedSensorManager
- CMHeadphoneMotionManager
- CMWaterSubmersionManager
- Accessing submersion data
- Getting processed device-motion data
{
"skill_name": "core-motion",
"evals": [
{
"id": 0,
"prompt": "Build a concise SwiftUI tilt-control service for an iOS game. It should read device motion at a reasonable rate, avoid wasting battery, and clean up correctly when the view disappears.",
"expected_output": "A Core Motion implementation outline that uses one CMMotionManager, includes NSMotionUsageDescription, checks availability/reference frames, starts device-motion or accelerometer updates at a suitable interval, handles errors/queues, and stops updates on lifecycle exit.",
"files": [],
"expectations": [
"Adds or calls out NSMotionUsageDescription before accessing motion data.",
"Uses a single shared CMMotionManager instead of one manager per view.",
"Checks isDeviceMotionAvailable or the relevant sensor availability before starting updates.",
"Chooses a modest update interval based on the interaction instead of defaulting to the highest possible rate.",
"Stops the matching updates when the view or service is no longer active.",
"Checks available attitude reference frames before requesting a north-based frame."
]
},
{
"id": 1,
"prompt": "Review this plan for a hiking app: use CMPedometer for daily steps, CMMotionActivityManager to switch walking/driving modes, and CMAltimeter for elevation. The team says absolute altitude is GPS-based, activity flags are mutually exclusive, and only activity recognition needs motion permission.",
"expected_output": "A correction-focused review that covers NSMotionUsageDescription and authorization checks for pedometer/activity/altimeter, nonexclusive CMMotionActivity flags with confidence, and absolute altitude as sea-level altitude on supported hardware rather than GPS-based data.",
"files": [],
"expectations": [
"Requires NSMotionUsageDescription for the Core Motion APIs in the plan.",
"Checks CMAuthorizationStatus through the relevant pedometer, activity, or altimeter manager APIs before relying on data.",
"Explains that CMMotionActivity flags are not mutually exclusive and should be interpreted with confidence.",
"Describes absolute altitude as altitude relative to sea level, not GPS-based altitude.",
"Mentions that absolute altitude requires supported hardware such as iPhone 12 or later or Apple Watch Series 6/SE or later.",
"Keeps the answer in Core Motion scope rather than turning it into a HealthKit workout or SensorKit research guide."
]
},
{
"id": 2,
"prompt": "I need advanced motion guidance for a watchOS app that analyzes golf swings with batched accelerometer data, tracks AirPods head motion during coaching, and supports shallow dive depth on Apple Watch Series 10/Ultra. What setup and API pitfalls should I avoid?",
"expected_output": "An advanced Core Motion guide that uses CMBatchedSensorManager safely without assigning read-only frequency properties, covers CMHeadphoneMotionManager privacy/connection behavior, and sets up CMWaterSubmersionManager with availability checks, motion usage text, Shallow Depth and Pressure or full entitlement, and underwater-depth background mode.",
"files": [],
"expectations": [
"Uses CMBatchedSensorManager only after checking support and authorization.",
"Does not assign accelerometerDataFrequency or deviceMotionDataFrequency and instead treats them as reported read-only frequencies.",
"Mentions watchOS 10+ async batched update sequences or otherwise gates batched update APIs by availability.",
"Includes NSMotionUsageDescription for headphone motion and submersion access.",
"Uses CMHeadphoneMotionManager delegate or connection-status updates for connect/disconnect behavior.",
"Checks CMWaterSubmersionManager.waterSubmersionAvailable before instantiating the manager.",
"Distinguishes Shallow Depth and Pressure capability for 6-meter dives from the full Submerged Depth and Pressure entitlement for 40-meter dives.",
"Adds WKBackgroundModes with underwater-depth for dive sessions/autolaunch and does not claim only Apple Watch Ultra can support shallow depth."
]
}
]
}
CoreMotion Extended Patterns
Overflow reference for the core-motion skill. Contains advanced patterns that exceed the main skill file's scope.
Contents
- SwiftUI Integration with `@Observable`
- CMBatchedSensorManager (High-Frequency)
- Headphone Motion
- Pedometer SwiftUI View
- Activity-Based Navigation
- Water Submersion (watchOS)
SwiftUI Integration with @Observable
Motion Manager Service
import CoreMotion
import SwiftUI
@Observable
@MainActor
final class MotionService {
static let shared = MotionService()
private let manager = CMMotionManager()
var pitch: Double = 0
var roll: Double = 0
var yaw: Double = 0
var userAcceleration: CMAcceleration = CMAcceleration()
var isActive = false
func startDeviceMotion(interval: TimeInterval = 1.0 / 60.0) {
guard manager.isDeviceMotionAvailable, !isActive else { return }
manager.deviceMotionUpdateInterval = interval
manager.startDeviceMotionUpdates(
using: .xArbitraryZVertical,
to: .main
) { [weak self] motion, error in
guard let self, let motion else { return }
self.pitch = motion.attitude.pitch
self.roll = motion.attitude.roll
self.yaw = motion.attitude.yaw
self.userAcceleration = motion.userAcceleration
}
isActive = true
}
func stop() {
manager.stopDeviceMotionUpdates()
isActive = false
}
}SwiftUI View Using Motion
struct TiltView: View {
@State private var motionService = MotionService.shared
var body: some View {
VStack {
Circle()
.fill(.blue)
.frame(width: 60, height: 60)
.offset(
x: motionService.roll * 100,
y: motionService.pitch * 100
)
Text("Roll: \(motionService.roll, format: .number.precision(.fractionLength(2)))")
Text("Pitch: \(motionService.pitch, format: .number.precision(.fractionLength(2)))")
}
.onAppear { motionService.startDeviceMotion() }
.onDisappear { motionService.stop() }
}
}Level Indicator
struct LevelIndicator: View {
@State private var motionService = MotionService.shared
private var isLevel: Bool {
abs(motionService.pitch) < 0.05 && abs(motionService.roll) < 0.05
}
var body: some View {
ZStack {
Circle()
.stroke(isLevel ? .green : .gray, lineWidth: 3)
.frame(width: 200, height: 200)
Circle()
.fill(isLevel ? .green : .red)
.frame(width: 20, height: 20)
.offset(
x: motionService.roll * 100,
y: motionService.pitch * -100
)
}
.onAppear { motionService.startDeviceMotion(interval: 1.0 / 30.0) }
.onDisappear { motionService.stop() }
}
}CMBatchedSensorManager (High-Frequency)
CMBatchedSensorManager delivers batches of high-frequency accelerometer and device-motion data for workout-style motion analysis, such as golf swings or bat swings. The async update sequences are watchOS 10+ APIs; check availability and authorization before starting updates.
AsyncSequence Pattern
import CoreMotion
@Observable
@MainActor
final class BatchedMotionService {
private let batchedManager = CMBatchedSensorManager()
private var updateTask: Task<Void, Never>?
var latestAcceleration: CMAcceleration?
func startBatchedAccelerometer() {
let authorization = CMBatchedSensorManager.authorizationStatus
guard CMBatchedSensorManager.isAccelerometerSupported,
authorization != .denied,
authorization != .restricted else { return }
updateTask = Task {
for await batch in batchedManager.accelerometerUpdates() {
guard !Task.isCancelled else { break }
// Process entire batch
for sample in batch {
// sample.acceleration, sample.timestamp
}
// Update UI with most recent
if let latest = batch.last {
latestAcceleration = latest.acceleration
}
}
}
}
func stop() {
updateTask?.cancel()
updateTask = nil
batchedManager.stopAccelerometerUpdates()
}
}Reading Frequency
let batchedManager = CMBatchedSensorManager()
// Start updates, then read the frequency the device reports.
batchedManager.startAccelerometerUpdates()
let reportedHz = batchedManager.accelerometerDataFrequencyaccelerometerDataFrequency and deviceMotionDataFrequency are read-only. Do not assign them; use the reported values to size buffers, throttle UI updates, or downsample processed results.
Headphone Motion
Track head motion using AirPods Pro / AirPods Max via CMHeadphoneMotionManager. On iOS and macOS, include NSMotionUsageDescription. Use connection-status updates when the app needs connect/disconnect events outside an active motion session.
import CoreMotion
@Observable
@MainActor
final class HeadphoneMotionService: NSObject {
private let headphoneManager = CMHeadphoneMotionManager()
var isConnected = false
var headPitch: Double = 0
var headYaw: Double = 0
func start() {
guard headphoneManager.isDeviceMotionAvailable else { return }
headphoneManager.delegate = self
headphoneManager.startConnectionStatusUpdates()
headphoneManager.startDeviceMotionUpdates(to: .main) { [weak self] motion, error in
guard let self, let motion else { return }
self.headPitch = motion.attitude.pitch
self.headYaw = motion.attitude.yaw
}
}
func stop() {
headphoneManager.stopDeviceMotionUpdates()
headphoneManager.stopConnectionStatusUpdates()
}
}
extension HeadphoneMotionService: CMHeadphoneMotionManagerDelegate {
nonisolated func headphoneMotionManagerDidConnect(
_ manager: CMHeadphoneMotionManager
) {
Task { @MainActor in isConnected = true }
}
nonisolated func headphoneMotionManagerDidDisconnect(
_ manager: CMHeadphoneMotionManager
) {
Task { @MainActor in isConnected = false }
}
}Pedometer SwiftUI View
Step Counter Dashboard
import CoreMotion
import SwiftUI
@Observable
@MainActor
final class PedometerService {
private let pedometer = CMPedometer()
var todaySteps: Int = 0
var todayDistance: Double = 0
var floorsAscended: Int = 0
func fetchToday() {
guard CMPedometer.isStepCountingAvailable() else { return }
let startOfDay = Calendar.current.startOfDay(for: Date())
pedometer.queryPedometerData(from: startOfDay, to: Date()) { [weak self] data, error in
guard let self, let data else { return }
Task { @MainActor in
self.todaySteps = data.numberOfSteps.intValue
self.todayDistance = data.distance?.doubleValue ?? 0
self.floorsAscended = data.floorsAscended?.intValue ?? 0
}
}
}
func startLiveUpdates() {
guard CMPedometer.isStepCountingAvailable() else { return }
let startOfDay = Calendar.current.startOfDay(for: Date())
pedometer.startUpdates(from: startOfDay) { [weak self] data, error in
guard let self, let data else { return }
Task { @MainActor in
self.todaySteps = data.numberOfSteps.intValue
self.todayDistance = data.distance?.doubleValue ?? 0
self.floorsAscended = data.floorsAscended?.intValue ?? 0
}
}
}
func stopLiveUpdates() {
pedometer.stopUpdates()
}
}SwiftUI Dashboard View
struct StepDashboard: View {
@State private var pedometerService = PedometerService()
var body: some View {
List {
Section("Today") {
LabeledContent("Steps") {
Text("\(pedometerService.todaySteps)")
}
LabeledContent("Distance") {
Text(
Measurement(value: pedometerService.todayDistance, unit: UnitLength.meters),
format: .measurement(width: .abbreviated)
)
}
if CMPedometer.isFloorCountingAvailable() {
LabeledContent("Floors Climbed") {
Text("\(pedometerService.floorsAscended)")
}
}
}
}
.onAppear { pedometerService.startLiveUpdates() }
.onDisappear { pedometerService.stopLiveUpdates() }
}
}Activity-Based Navigation
Switch between driving and walking modes automatically:
import CoreMotion
@Observable
@MainActor
final class NavigationModeService {
private let activityManager = CMMotionActivityManager()
enum Mode: String {
case walking, driving, cycling, unknown
}
var currentMode: Mode = .unknown
func startMonitoring() {
guard CMMotionActivityManager.isActivityAvailable() else { return }
activityManager.startActivityUpdates(to: .main) { [weak self] activity in
guard let self, let activity,
activity.confidence != .low else { return }
Task { @MainActor in
if activity.automotive {
self.currentMode = .driving
} else if activity.cycling {
self.currentMode = .cycling
} else if activity.walking || activity.running {
self.currentMode = .walking
} else if activity.stationary {
// Keep previous mode when stationary (e.g., at a stoplight)
}
}
}
}
func stopMonitoring() {
activityManager.stopActivityUpdates()
}
}Water Submersion (watchOS)
Track water depth and temperature for dive apps on supported Apple Watch hardware. Use waterSubmersionAvailable rather than hard-coding model checks: Apple Watch Ultra supports submersion data, and Apple Watch Series 10 supports the Shallow Depth and Pressure capability.
Setup checklist:
- Add
NSMotionUsageDescription. - Add the Shallow Depth and Pressure capability for dives up to 6 meters, or
apply for the full Submerged Depth and Pressure entitlement for dives up to 40 meters.
- Add
WKBackgroundModeswithunderwater-depthso the app can remain
frontmost and eligible for dive autolaunch.
- Check availability before instantiating
CMWaterSubmersionManager.
import CoreMotion
@Observable
@MainActor
final class DiveService: NSObject {
private var submersionManager: CMWaterSubmersionManager?
var isSubmerged = false
var currentDepth: Double?
var waterTemperature: Double?
func start() {
guard CMWaterSubmersionManager.waterSubmersionAvailable else { return }
let manager = CMWaterSubmersionManager()
manager.delegate = self
submersionManager = manager
}
}
extension DiveService: CMWaterSubmersionManagerDelegate {
nonisolated func manager(
_ manager: CMWaterSubmersionManager,
didUpdate event: CMWaterSubmersionEvent
) {
Task { @MainActor in
isSubmerged = event.state == .submerged
}
}
nonisolated func manager(
_ manager: CMWaterSubmersionManager,
didUpdate measurement: CMWaterSubmersionMeasurement
) {
Task { @MainActor in
currentDepth = measurement.depth?.value
}
}
nonisolated func manager(
_ manager: CMWaterSubmersionManager,
didUpdate temperature: CMWaterTemperature
) {
Task { @MainActor in
waterTemperature = temperature.temperature.value
}
}
nonisolated func manager(
_ manager: CMWaterSubmersionManager,
errorOccurred error: any Error
) {
print("Submersion error: \(error)")
}
}Important: CMWaterSubmersionManager requires the Shallow Depth and Pressure capability or the full Submerged Depth and Pressure entitlement. If the app lacks the entitlement, the delegate receives CMError.notEntitled and no submersion data.
Related skills
FAQ
Why does motion access crash on launch?
Add NSMotionUsageDescription to Info.plist before calling motion APIs.
How should games read accelerometer data?
Start updates without a handler and poll accelerometerData each frame from one CMMotionManager.
Which attitude frame should I request?
Check availableAttitudeReferenceFrames and fall back when corrected or north-aligned frames are unavailable.
Is Core Motion safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.