Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Access 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/running/driving/cycling, tracking altitude, building motion interactions, handling AirPods head tracking, or implementing watchOS dive/depth features.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 121% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 94% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 80% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 607% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 125% | 0% |
Read device motion, pedometer/activity, altitude, headphone, batched-workout, and submersion sensors with Core Motion. Scope: Swift 6.3, iOS 26+.
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.
xml<key>NSMotionUsageDescription</key> <string>This app uses motion data to track your activity.</string>
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.
swiftimport 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 }
Create exactly one CMMotionManager per app. Multiple instances degrade sensor update rates.
swiftimport CoreMotion let motionManager = CMMotionManager()
swiftguard 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()
swiftguard 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()
For games, start updates without a handler and poll the latest sample each frame:
swiftmotionManager.startAccelerometerUpdates() // In your game loop / display link: if let data = motionManager.accelerometerData { let tilt = data.acceleration.x // Move player based on tilt }
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.
swiftguard 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()
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:
swiftlet available = CMMotionManager.availableAttitudeReferenceFrames() if available.contains(.xTrueNorthZVertical) { // Safe to use true north }
CMPedometer provides step counts, distance, pace, cadence, and floor counts.
swiftlet 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()
| Method | What It Checks | |---|---| | isStepCountingAvailable() | Step counter hardware | | isDistanceAvailable() | Distance estimation | | isFloorCountingAvailable() | Barometric altimeter for floors | | isPaceAvailable() | Pace data | | isCadenceAvailable() | Cadence data |
Detects whether the user is stationary, walking, running, cycling, or in a vehicle.
swiftlet 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()
swiftlet 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)") } }
Altimeter access is covered by NSMotionUsageDescription; handle denied motion access through unavailable data and update-handler errors.
swiftlet 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.
swiftguard 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()
| 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.
Retain one app-level CMMotionManager; competing instances can reduce update rates.
Apply the matching is...Available gate immediately before starting each sensor stream.
Pair every start with the matching stop in the counterpart lifecycle or task cancellation path.
Choose the lowest rate that meets the interaction and use the Update Intervals and Battery table as a starting point.
swift// 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() }
NSMotionUsageDescription present in Info.plist with a clear explanationCMMotionManager instance shared across the appisAccelerometerAvailable, etc.)start*Updates calls have matching stop*Updates in lifecycle counterpartsCMMotionActivity.confidence checked before acting on activity typeCMMotionManager.availableAttitudeReferenceFrames() before requesting a specific attitude frameOther measured skills in the registry, with their headline benchmark lift.