$npx -y skills add dpearson2699/swift-ios-skills --skill core-motionAccess Core Motion accelerometer, gyroscope, magnetometer, device-motion, pedometer, activity-recognition, altitude, headphone motion, batched high-frequency workout motion, and water-submersion/depth data. Use when reading device sensors, counting steps, detecting walking/runnin
| 1 | # CoreMotion |
| 2 | |
| 3 | Read device motion, pedometer/activity, altitude, headphone, batched-workout, |
| 4 | and submersion sensors with Core Motion. Scope: Swift 6.3, iOS 26+. |
| 5 | |
| 6 | ## Contents |
| 7 | |
| 8 | - [Setup](#setup) |
| 9 | - [CMMotionManager: Sensor Data](#cmmotionmanager-sensor-data) |
| 10 | - [Processed Device Motion](#processed-device-motion) |
| 11 | - [CMPedometer: Step and Distance Data](#cmpedometer-step-and-distance-data) |
| 12 | - [CMMotionActivityManager: Activity Recognition](#cmmotionactivitymanager-activity-recognition) |
| 13 | - [CMAltimeter: Altitude Data](#cmaltimeter-altitude-data) |
| 14 | - [Update Intervals and Battery](#update-intervals-and-battery) |
| 15 | - [Common Mistakes](#common-mistakes) |
| 16 | - [Review Checklist](#review-checklist) |
| 17 | - [References](#references) |
| 18 | |
| 19 | ## Setup |
| 20 | |
| 21 | ### Info.plist |
| 22 | |
| 23 | Add `NSMotionUsageDescription` to Info.plist with a user-facing string explaining |
| 24 | why your app needs motion data. Without this key, the app crashes on first access. |
| 25 | |
| 26 | ```xml |
| 27 | <key>NSMotionUsageDescription</key> |
| 28 | <string>This app uses motion data to track your activity.</string> |
| 29 | ``` |
| 30 | |
| 31 | ### Authorization |
| 32 | |
| 33 | Use the matching manager's `authorizationStatus()` or `authorizationStatus` |
| 34 | property when an API exposes one (`CMPedometer`, `CMMotionActivityManager`, |
| 35 | `CMAltimeter`, headphone motion, batched sensors, and submersion). Raw |
| 36 | `CMMotionManager` accelerometer/gyro/device-motion streams have no explicit |
| 37 | authorization request API; still ship the usage string and handle errors from |
| 38 | start/update callbacks. |
| 39 | |
| 40 | ```swift |
| 41 | import CoreMotion |
| 42 | |
| 43 | let status = CMMotionActivityManager.authorizationStatus() |
| 44 | switch status { |
| 45 | case .notDetermined: |
| 46 | // Will prompt on first use |
| 47 | break |
| 48 | case .authorized: |
| 49 | break |
| 50 | case .restricted, .denied: |
| 51 | // Direct user to Settings |
| 52 | break |
| 53 | @unknown default: |
| 54 | break |
| 55 | } |
| 56 | ``` |
| 57 | |
| 58 | ## CMMotionManager: Sensor Data |
| 59 | |
| 60 | Create exactly **one** `CMMotionManager` per app. Multiple instances degrade |
| 61 | sensor update rates. |
| 62 | |
| 63 | ```swift |
| 64 | import CoreMotion |
| 65 | |
| 66 | let motionManager = CMMotionManager() |
| 67 | ``` |
| 68 | |
| 69 | ### Accelerometer Updates |
| 70 | |
| 71 | ```swift |
| 72 | guard motionManager.isAccelerometerAvailable else { return } |
| 73 | |
| 74 | motionManager.accelerometerUpdateInterval = 1.0 / 60.0 // 60 Hz |
| 75 | |
| 76 | motionManager.startAccelerometerUpdates(to: .main) { data, error in |
| 77 | guard let acceleration = data?.acceleration else { return } |
| 78 | print("x: \(acceleration.x), y: \(acceleration.y), z: \(acceleration.z)") |
| 79 | } |
| 80 | |
| 81 | // When done: |
| 82 | motionManager.stopAccelerometerUpdates() |
| 83 | ``` |
| 84 | |
| 85 | ### Gyroscope Updates |
| 86 | |
| 87 | ```swift |
| 88 | guard motionManager.isGyroAvailable else { return } |
| 89 | |
| 90 | motionManager.gyroUpdateInterval = 1.0 / 60.0 |
| 91 | |
| 92 | motionManager.startGyroUpdates(to: .main) { data, error in |
| 93 | guard let rotationRate = data?.rotationRate else { return } |
| 94 | print("x: \(rotationRate.x), y: \(rotationRate.y), z: \(rotationRate.z)") |
| 95 | } |
| 96 | |
| 97 | motionManager.stopGyroUpdates() |
| 98 | ``` |
| 99 | |
| 100 | ### Polling Pattern (Games) |
| 101 | |
| 102 | For games, start updates without a handler and poll the latest sample each frame: |
| 103 | |
| 104 | ```swift |
| 105 | motionManager.startAccelerometerUpdates() |
| 106 | |
| 107 | // In your game loop / display link: |
| 108 | if let data = motionManager.accelerometerData { |
| 109 | let tilt = data.acceleration.x |
| 110 | // Move player based on tilt |
| 111 | } |
| 112 | ``` |
| 113 | |
| 114 | ## Processed Device Motion |
| 115 | |
| 116 | Device motion fuses accelerometer, gyroscope, and magnetometer into a single |
| 117 | `CMDeviceMotion` object with attitude, user acceleration (gravity removed), |
| 118 | rotation rate, and calibrated magnetic field. |
| 119 | |
| 120 | When giving device-motion guidance, show the runtime frame check in the snippet |
| 121 | instead of hard-coding a corrected, magnetic-north, or true-north frame. Fall |
| 122 | back to `.xArbitraryZVertical` when the preferred frame is unavailable. |
| 123 | |
| 124 | ```swift |
| 125 | guard motionManager.isDeviceMotionAvailable else { return } |
| 126 | |
| 127 | let availableFrames = CMMotionManager.availableAttitudeReferenceFrames() |
| 128 | let frame: CMAttitudeReferenceFrame = availableFrames.contains(.xArbitraryCorrectedZVertical) |
| 129 | ? .xArbitraryCorrectedZVertical |
| 130 | : .xArbitraryZVertical |
| 131 | |
| 132 | motionManager.deviceMotionUpdateInterval = 1.0 / 60.0 |
| 133 | |
| 134 | motionManager.startDeviceMotionUpdates( |
| 135 | using: frame, |
| 136 | to: .main |
| 137 | ) { motion, error in |
| 138 | guard let motion else { return } |
| 139 | |
| 140 | let attitude = motion.attitude // roll, pitch, yaw |
| 141 | let userAccel = motion.userAcceleration |
| 142 | let gravity = motion.gravity |
| 143 | let heading = motion.heading // degrees relative to the current frame |
| 144 | |
| 145 | print("Pitch: \(attitude.pitch), Roll: \(attitude.roll)") |
| 146 | } |
| 147 | |
| 148 | motionManager.stopDeviceMotionUpdates() |
| 149 | ``` |
| 150 | |
| 151 | ### Attitude Reference Frames |
| 152 | |
| 153 | For simple tilt controls, use `.xArbitraryZVertical` or |
| 154 | `.xArbitraryCorrectedZVertical`; they avoid magnetometer/location dependencies. |
| 155 | Before request |