core-motion
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCoreMotion
CoreMotion
Read device sensor data -- accelerometer, gyroscope, magnetometer, pedometer, and
activity recognition -- 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.2 / iOS 26+.
在iOS和watchOS上读取设备传感器数据——包括加速度计、陀螺仪、磁力计、计步器和活动识别。CoreMotion将原始传感器输入融合为经过处理的设备运动数据,并为健身和导航场景提供计步器/活动API。目标适配Swift 6.2 / iOS 26+。
Contents
目录
Setup
配置
Info.plist
Info.plist
Add to Info.plist with a user-facing string explaining
why your app needs motion data. Without this key, the app crashes on first access.
NSMotionUsageDescriptionxml
<key>NSMotionUsageDescription</key>
<string>This app uses motion data to track your activity.</string>在Info.plist中添加键,并附上面向用户的字符串,说明应用需要运动数据的原因。如果没有此键,应用在首次访问时会崩溃。
NSMotionUsageDescriptionxml
<key>NSMotionUsageDescription</key>
<string>This app uses motion data to track your activity.</string>Authorization
授权
CoreMotion uses for pedometer and activity APIs. Sensor
APIs (accelerometer, gyro) do not require explicit authorization but do require
the usage description key.
CMAuthorizationStatusswift
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
}CoreMotion使用管理计步器和活动API的权限。传感器API(加速度计、陀螺仪)不需要显式授权,但必须添加上述使用描述键。
CMAuthorizationStatusswift
import CoreMotion
let status = CMMotionActivityManager.authorizationStatus()
switch status {
case .notDetermined:
// 首次使用时会弹出授权提示
break
case .authorized:
break
case .restricted, .denied:
// 引导用户前往设置页面开启权限
break
@unknown default:
break
}CMMotionManager: Sensor Data
CMMotionManager:传感器数据
Create exactly one per app. Multiple instances degrade
sensor update rates.
CMMotionManagerswift
import CoreMotion
let motionManager = CMMotionManager()每个应用仅创建一个实例。多个实例会降低传感器更新速率。
CMMotionManagerswift
import CoreMotion
let motionManager = CMMotionManager()Accelerometer Updates
加速度计更新
swift
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()swift
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)")
}
// 使用完毕后停止:
motionManager.stopAccelerometerUpdates()Gyroscope Updates
陀螺仪更新
swift
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()swift
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:
swift
motionManager.startAccelerometerUpdates()
// In your game loop / display link:
if let data = motionManager.accelerometerData {
let tilt = data.acceleration.x
// Move player based on tilt
}对于游戏,可在不设置处理程序的情况下启动更新,然后在每一帧轮询最新样本:
swift
motionManager.startAccelerometerUpdates()
// 在游戏循环/显示链接中:
if let data = motionManager.accelerometerData {
let tilt = data.acceleration.x
// 根据倾斜角度移动玩家
}Processed Device Motion
处理后的设备运动数据
Device motion fuses accelerometer, gyroscope, and magnetometer into a single
object with attitude, user acceleration (gravity removed),
rotation rate, and calibrated magnetic field.
CMDeviceMotionswift
guard motionManager.isDeviceMotionAvailable else { return }
motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
motionManager.startDeviceMotionUpdates(
using: .xArbitraryZVertical,
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 // 0-360 degrees (requires magnetometer)
print("Pitch: \(attitude.pitch), Roll: \(attitude.roll)")
}
motionManager.stopDeviceMotionUpdates()设备运动数据将加速度计、陀螺仪和磁力计的数据融合为单个对象,包含姿态、用户加速度(已去除重力影响)、旋转速率和校准后的磁场数据。
CMDeviceMotionswift
guard motionManager.isDeviceMotionAvailable else { return }
motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
motionManager.startDeviceMotionUpdates(
using: .xArbitraryZVertical,
to: .main
) { motion, error in
guard let motion else { return }
let attitude = motion.attitude // 横滚、俯仰、偏航
let userAccel = motion.userAcceleration
let gravity = motion.gravity
let heading = motion.heading // 0-360度(需要磁力计)
print("Pitch: \(attitude.pitch), Roll: \(attitude.roll)")
}
motionManager.stopDeviceMotionUpdates()Attitude Reference Frames
姿态参考坐标系
| Frame | Use Case |
|---|---|
| Default. Z is vertical, X arbitrary at start. Most games. |
| Same as above, corrected for gyro drift over time. |
| X points to magnetic north. Requires magnetometer. |
| X points to true north. Requires magnetometer + location. |
Check available frames before use:
swift
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
// Safe to use true north
}| 坐标系 | 使用场景 |
|---|---|
| 默认选项。Z轴垂直,X轴在启动时为任意方向。适用于大多数游戏。 |
| 与上述相同,但会随时间修正陀螺仪漂移。 |
| X轴指向磁北。需要磁力计。 |
| X轴指向真北。需要磁力计+定位服务。 |
使用前检查可用的坐标系:
swift
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
// 可以安全使用真北坐标系
}CMPedometer: Step and Distance Data
CMPedometer:步数与距离数据
CMPedometerswift
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()CMPedometerswift
let pedometer = CMPedometer()
guard CMPedometer.isStepCountingAvailable() else { return }
// 历史数据查询
pedometer.queryPedometerData(
from: Calendar.current.startOfDay(for: Date()),
to: Date()
) { data, error in
guard let data else { return }
print("今日步数: \(data.numberOfSteps)")
print("距离: \(data.distance?.doubleValue ?? 0) 米")
print("上升楼层数: \(data.floorsAscended?.intValue ?? 0)")
}
// 实时更新
pedometer.startUpdates(from: Date()) { data, error in
guard let data else { return }
print("步数: \(data.numberOfSteps)")
}
// 使用完毕后停止
pedometer.stopUpdates()Availability Checks
可用性检查
| Method | What It Checks |
|---|---|
| Step counter hardware |
| Distance estimation |
| Barometric altimeter for floors |
| Pace data |
| Cadence data |
| 方法 | 检查内容 |
|---|---|
| 计步器硬件是否存在 |
| 距离估算功能是否可用 |
| 用于楼层计数的气压高度计是否存在 |
| 步速数据是否可用 |
| 步频数据是否可用 |
CMMotionActivityManager: Activity Recognition
CMMotionActivityManager:活动识别
Detects whether the user is stationary, walking, running, cycling, or in a vehicle.
swift
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()检测用户当前是否处于静止、步行、跑步、骑行或乘车状态。
swift
let activityManager = CMMotionActivityManager()
guard CMMotionActivityManager.isActivityAvailable() else { return }
// 实时活动更新
activityManager.startActivityUpdates(to: .main) { activity in
guard let activity else { return }
if activity.walking {
print("步行 (置信度: \(activity.confidence.rawValue))")
} else if activity.running {
print("跑步")
} else if activity.automotive {
print("乘车")
} else if activity.cycling {
print("骑行")
} else if activity.stationary {
print("静止")
}
}
activityManager.stopActivityUpdates()Historical Activity Query
历史活动查询
swift
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)")
}
}swift
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
CMAltimeter:海拔数据
swift
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()For absolute altitude (GPS-based):
swift
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()swift
let altimeter = CMAltimeter()
guard CMAltimeter.isRelativeAltitudeAvailable() else { return }
altimeter.startRelativeAltitudeUpdates(to: .main) { data, error in
guard let data else { return }
print("相对海拔: \(data.relativeAltitude) 米")
print("气压: \(data.pressure) kPa")
}
altimeter.stopRelativeAltitudeUpdates()获取绝对海拔(基于GPS):
swift
guard CMAltimeter.isAbsoluteAltitudeAvailable() else { return }
altimeter.startAbsoluteAltitudeUpdates(to: .main) { data, error in
guard let data else { return }
print("海拔: \(data.altitude)m, 精度: \(data.accuracy)m")
}
altimeter.stopAbsoluteAltitudeUpdates()Update Intervals and Battery
更新间隔与电池消耗
| Interval | Hz | Use Case | Battery Impact |
|---|---|---|---|
| 10 | UI orientation | Low |
| 30 | Casual games | Moderate |
| 60 | Action games | High |
| 100 | Max rate (iPhone) | Very High |
Use the lowest frequency that meets your needs. caps at 100 Hz
per sample. For higher frequencies, use on watchOS/iOS 17+.
CMMotionManagerCMBatchedSensorManager| 间隔 | 帧率 | 使用场景 | 电池影响 |
|---|---|---|---|
| 10 | UI方向检测 | 低 |
| 30 | 休闲游戏 | 中等 |
| 60 | 动作游戏 | 高 |
| 100 | 最大速率(iPhone) | 极高 |
使用满足需求的最低频率。每个样本的帧率上限为100Hz。如需更高频率,可在watchOS/iOS 17+上使用。
CMMotionManagerCMBatchedSensorManagerCommon Mistakes
常见错误
DON'T: Create multiple CMMotionManager instances
错误做法:创建多个CMMotionManager实例
swift
// 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()
}swift
// 错误——会降低所有实例的更新速率
class ViewA { let motion = CMMotionManager() }
class ViewB { let motion = CMMotionManager() }
// 正确——单个实例,在应用内共享
@Observable
final class MotionService {
static let shared = MotionService()
let manager = CMMotionManager()
}DON'T: Skip sensor availability checks
错误做法:跳过传感器可用性检查
swift
// 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 }swift
// 错误——在没有陀螺仪的设备上会崩溃
motionManager.startGyroUpdates(to: .main) { data, _ in }
// 正确——先检查可用性
guard motionManager.isGyroAvailable else {
showUnsupportedMessage()
return
}
motionManager.startGyroUpdates(to: .main) { data, _ in }DON'T: Forget to stop updates
错误做法:忘记停止更新
swift
// 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()
}swift
// 错误——更新会持续运行,消耗电池
class MotionVC: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
motionManager.startAccelerometerUpdates(to: .main) { _, _ in }
}
// 缺少viewDidDisappear中的停止代码!
}
// 正确——在对应的生命周期方法中停止
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
motionManager.stopAccelerometerUpdates()
}DON'T: Use unnecessarily high update rates
错误做法:使用不必要的高更新速率
swift
// 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.0swift
// 错误——指南针显示使用100Hz
motionManager.deviceMotionUpdateInterval = 1.0 / 100.0
// 正确——指南针使用10Hz已足够
motionManager.deviceMotionUpdateInterval = 1.0 / 10.0DON'T: Assume all CMMotionActivity properties are mutually exclusive
错误做法:假设CMMotionActivity的所有属性互斥
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()
}swift
// 错误——仅检查一个属性
if activity.walking { handleWalking() }
// 正确——多个属性可能同时为真;需检查置信度
if activity.walking && activity.confidence == .high {
handleWalking()
} else if activity.automotive && activity.confidence != .low {
handleDriving()
}Review Checklist
审核清单
- present in Info.plist with a clear explanation
NSMotionUsageDescription - Single instance shared across the app
CMMotionManager - Sensor availability checked before starting updates (, etc.)
isAccelerometerAvailable - Authorization status checked before pedometer/activity APIs
- Update interval set to the lowest acceptable frequency
- All calls have matching
start*Updatesin lifecycle counterpartsstop*Updates - Handlers dispatched to appropriate queues (not blocking main for heavy processing)
- checked before acting on activity type
CMMotionActivity.confidence - Error parameters checked in update handlers
- Attitude reference frame chosen based on actual need (not defaulting to true north unnecessarily)
- Info.plist中已添加,且说明清晰
NSMotionUsageDescription - 应用内共享单个实例
CMMotionManager - 启动更新前已检查传感器可用性(等)
isAccelerometerAvailable - 使用计步器/活动API前已检查授权状态
- 更新间隔设置为可接受的最低频率
- 所有调用都在对应的生命周期方法中有匹配的
start*Updatesstop*Updates - 处理程序分发到合适的队列(不会因繁重处理阻塞主线程)
- 处理活动类型前已检查
CMMotionActivity.confidence - 更新处理程序中已检查错误参数
- 根据实际需求选择姿态参考坐标系(不不必要地默认使用真北)
References
参考资料
- Extended patterns (SwiftUI integration, batched sensor manager, headphone motion):
references/motion-patterns.md - CoreMotion framework
- CMMotionManager
- CMPedometer
- CMMotionActivityManager
- CMDeviceMotion
- CMAltimeter
- CMBatchedSensorManager
- Getting processed device-motion data
- 扩展模式(SwiftUI集成、批量传感器管理器、耳机运动):
references/motion-patterns.md - CoreMotion框架
- CMMotionManager
- CMPedometer
- CMMotionActivityManager
- CMDeviceMotion
- CMAltimeter
- CMBatchedSensorManager
- 获取处理后的设备运动数据