core-motion

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CoreMotion

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
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>
在Info.plist中添加
NSMotionUsageDescription
键,并附上面向用户的字符串,说明应用需要运动数据的原因。如果没有此键,应用在首次访问时会崩溃。
xml
<key>NSMotionUsageDescription</key>
<string>This app uses motion data to track your activity.</string>

Authorization

授权

CoreMotion uses
CMAuthorizationStatus
for pedometer and activity APIs. Sensor APIs (accelerometer, gyro) do not require explicit authorization but do require the usage description key.
swift
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使用
CMAuthorizationStatus
管理计步器和活动API的权限。传感器API(加速度计、陀螺仪)不需要显式授权,但必须添加上述使用描述键。
swift
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
CMMotionManager
per app. Multiple instances degrade sensor update rates.
swift
import CoreMotion

let motionManager = CMMotionManager()
每个应用仅创建一个
CMMotionManager
实例。多个实例会降低传感器更新速率。
swift
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
CMDeviceMotion
object with attitude, user acceleration (gravity removed), rotation rate, and calibrated magnetic field.
swift
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()
设备运动数据将加速度计、陀螺仪和磁力计的数据融合为单个
CMDeviceMotion
对象,包含姿态、用户加速度(已去除重力影响)、旋转速率和校准后的磁场数据。
swift
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

姿态参考坐标系

FrameUse 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:
swift
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
    // Safe to use true north
}
坐标系使用场景
.xArbitraryZVertical
默认选项。Z轴垂直,X轴在启动时为任意方向。适用于大多数游戏。
.xArbitraryCorrectedZVertical
与上述相同,但会随时间修正陀螺仪漂移。
.xMagneticNorthZVertical
X轴指向磁北。需要磁力计。
.xTrueNorthZVertical
X轴指向真北。需要磁力计+定位服务。
使用前检查可用的坐标系:
swift
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
    // 可以安全使用真北坐标系
}

CMPedometer: Step and Distance Data

CMPedometer:步数与距离数据

CMPedometer
provides step counts, distance, pace, cadence, and floor counts.
swift
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()
CMPedometer
提供步数统计、距离、步速、步频和楼层计数功能。
swift
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

可用性检查

MethodWhat It Checks
isStepCountingAvailable()
Step counter hardware
isDistanceAvailable()
Distance estimation
isFloorCountingAvailable()
Barometric altimeter for floors
isPaceAvailable()
Pace data
isCadenceAvailable()
Cadence data
方法检查内容
isStepCountingAvailable()
计步器硬件是否存在
isDistanceAvailable()
距离估算功能是否可用
isFloorCountingAvailable()
用于楼层计数的气压高度计是否存在
isPaceAvailable()
步速数据是否可用
isCadenceAvailable()
步频数据是否可用

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

更新间隔与电池消耗

IntervalHzUse CaseBattery Impact
1.0 / 10.0
10UI orientationLow
1.0 / 30.0
30Casual gamesModerate
1.0 / 60.0
60Action gamesHigh
1.0 / 100.0
100Max rate (iPhone)Very High
Use the lowest frequency that meets your needs.
CMMotionManager
caps at 100 Hz per sample. For higher frequencies, use
CMBatchedSensorManager
on watchOS/iOS 17+.
间隔帧率使用场景电池影响
1.0 / 10.0
10UI方向检测
1.0 / 30.0
30休闲游戏中等
1.0 / 60.0
60动作游戏
1.0 / 100.0
100最大速率(iPhone)极高
使用满足需求的最低频率。
CMMotionManager
每个样本的帧率上限为100Hz。如需更高频率,可在watchOS/iOS 17+上使用
CMBatchedSensorManager

Common 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.0
swift
// 错误——指南针显示使用100Hz
motionManager.deviceMotionUpdateInterval = 1.0 / 100.0

// 正确——指南针使用10Hz已足够
motionManager.deviceMotionUpdateInterval = 1.0 / 10.0

DON'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

审核清单

  • NSMotionUsageDescription
    present in Info.plist with a clear explanation
  • Single
    CMMotionManager
    instance 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*Updates
    calls have matching
    stop*Updates
    in lifecycle counterparts
  • Handlers dispatched to appropriate queues (not blocking main for heavy processing)
  • CMMotionActivity.confidence
    checked before acting on activity type
  • 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*Updates
    调用都在对应的生命周期方法中有匹配的
    stop*Updates
  • 处理程序分发到合适的队列(不会因繁重处理阻塞主线程)
  • 处理活动类型前已检查
    CMMotionActivity.confidence
  • 更新处理程序中已检查错误参数
  • 根据实际需求选择姿态参考坐标系(不不必要地默认使用真北)

References

参考资料