Loading...
Loading...
Compare original and translation side by side
NSMotionUsageDescription<key>NSMotionUsageDescription</key>
<string>This app uses motion data to track your activity.</string>NSMotionUsageDescription<key>NSMotionUsageDescription</key>
<string>This app uses motion data to track your activity.</string>CMAuthorizationStatusimport 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
}CMAuthorizationStatusimport CoreMotion
let status = CMMotionActivityManager.authorizationStatus()
switch status {
case .notDetermined:
// 首次使用时会弹出授权提示
break
case .authorized:
break
case .restricted, .denied:
// 引导用户前往设置页面开启权限
break
@unknown default:
break
}CMMotionManagerimport CoreMotion
let motionManager = CMMotionManager()CMMotionManagerimport CoreMotion
let motionManager = CMMotionManager()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()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()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()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()motionManager.startAccelerometerUpdates()
// In your game loop / display link:
if let data = motionManager.accelerometerData {
let tilt = data.acceleration.x
// Move player based on tilt
}motionManager.startAccelerometerUpdates()
// 在游戏循环/显示链接中:
if let data = motionManager.accelerometerData {
let tilt = data.acceleration.x
// 根据倾斜角度移动玩家
}CMDeviceMotionguard 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()CMDeviceMotionguard 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()| 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. |
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
// Safe to use true north
}| 坐标系 | 使用场景 |
|---|---|
| 默认选项。Z轴垂直,X轴在启动时为任意方向。适用于大多数游戏。 |
| 与上述相同,但会随时间修正陀螺仪漂移。 |
| X轴指向磁北。需要磁力计。 |
| X轴指向真北。需要磁力计+定位服务。 |
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
// 可以安全使用真北坐标系
}CMPedometerlet 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()CMPedometerlet 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()| Method | What It Checks |
|---|---|
| Step counter hardware |
| Distance estimation |
| Barometric altimeter for floors |
| Pace data |
| Cadence data |
| 方法 | 检查内容 |
|---|---|
| 计步器硬件是否存在 |
| 距离估算功能是否可用 |
| 用于楼层计数的气压高度计是否存在 |
| 步速数据是否可用 |
| 步频数据是否可用 |
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()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()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)")
}
}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)")
}
}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()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()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()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()| Interval | Hz | Use Case | Battery Impact |
|---|---|---|---|
| 10 | UI orientation | Low |
| 30 | Casual games | Moderate |
| 60 | Action games | High |
| 100 | Max rate (iPhone) | Very High |
CMMotionManagerCMBatchedSensorManager| 间隔 | 帧率 | 使用场景 | 电池影响 |
|---|---|---|---|
| 10 | UI方向检测 | 低 |
| 30 | 休闲游戏 | 中等 |
| 60 | 动作游戏 | 高 |
| 100 | 最大速率(iPhone) | 极高 |
CMMotionManagerCMBatchedSensorManager// 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()
}// 错误——会降低所有实例的更新速率
class ViewA { let motion = CMMotionManager() }
class ViewB { let motion = CMMotionManager() }
// 正确——单个实例,在应用内共享
@Observable
final class MotionService {
static let shared = MotionService()
let manager = CMMotionManager()
}// 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 }// 错误——在没有陀螺仪的设备上会崩溃
motionManager.startGyroUpdates(to: .main) { data, _ in }
// 正确——先检查可用性
guard motionManager.isGyroAvailable else {
showUnsupportedMessage()
return
}
motionManager.startGyroUpdates(to: .main) { data, _ in }// 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()
}// 错误——更新会持续运行,消耗电池
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()
}// 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// 错误——指南针显示使用100Hz
motionManager.deviceMotionUpdateInterval = 1.0 / 100.0
// 正确——指南针使用10Hz已足够
motionManager.deviceMotionUpdateInterval = 1.0 / 10.0// 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()
}// 错误——仅检查一个属性
if activity.walking { handleWalking() }
// 正确——多个属性可能同时为真;需检查置信度
if activity.walking && activity.confidence == .high {
handleWalking()
} else if activity.automotive && activity.confidence != .low {
handleDriving()
}NSMotionUsageDescriptionCMMotionManagerisAccelerometerAvailablestart*Updatesstop*UpdatesCMMotionActivity.confidenceNSMotionUsageDescriptionCMMotionManagerisAccelerometerAvailablestart*Updatesstop*UpdatesCMMotionActivity.confidencereferences/motion-patterns.mdreferences/motion-patterns.md