core-motion について
Swift 6.3 と iOS 26 以降の開発者向け CoreMotion スキルです。モーション、歩数、アクティビティ認識、高度データの取得を扱い、センサーの可用性とバッテリー消費にも配慮します。
主な機能
- 01加速度計とジャイロスコープのデータ取得
- 02融合されたデバイスモーションデータの利用
- 03歩数、距離、階数の取得
- 04歩行やランニングなどの認識
- 05相対高度と絶対高度の取得
- 06更新間隔の設定とバッテリー管理
活用シーン
- 01傾きに基づくゲーム操作の構築
- 02歩数と移動距離の記録
- 03ユーザーのアクティビティ状態の認識
- 04デバイスの高度や気圧変化の監視
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill core-motionインストール後、Claude Code で/core-motionを入力して読み込まれたか確認します。
同梱ファイルevals/evals.jsonreferences/motion-patterns.md
詳細
CoreMotion
Core Motion を使用して、デバイスのモーション、歩数計/アクティビティ、高度、ヘッドホン、バッチ処理されたワークアウト、水没センサーのデータを読み取ります。対象範囲:Swift 6.3、iOS 26 以降。
目次
- セットアップ
- CMMotionManager:センサーデータ
- 処理済みデバイスモーション
- CMPedometer:歩数と距離のデータ
- CMMotionActivityManager:アクティビティ認識
- CMAltimeter:高度データ
- 更新間隔とバッテリー
- よくある間違い
- 確認チェックリスト
- 参考資料
セットアップ
Info.plist
Info.plist に NSMotionUsageDescription を追加し、アプリがモーションデータを必要とする理由をユーザーに説明する文字列を設定します。このキーがない場合、初回アクセス時にアプリがクラッシュします。
<key>NSMotionUsageDescription</key>
<string>This app uses motion data to track your activity.</string>
認証
API に認証ステータスを取得する方法がある場合は、対応するマネージャーの authorizationStatus() または authorizationStatus プロパティを使用します(CMPedometer、CMMotionActivityManager、CMAltimeter、ヘッドホンモーション、バッチセンサー、水没センサー)。生の CMMotionManager 加速度計/ジャイロスコープ/デバイスモーションのストリームには、明示的な認証リクエスト API がありません。それでも使用目的の文字列をアプリに含め、開始時や更新時のコールバックから返されるエラーを処理してください。
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
}
CMMotionManager:センサーデータ
アプリごとに CMMotionManager を1 つだけ作成してください。複数のインスタンスを作成すると、センサーの更新レートが低下します。
import 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.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
}
処理済みデバイスモーション
デバイスモーションは、加速度計、ジャイロスコープ、磁力計のデータを 1 つの CMDeviceMotion オブジェクトに統合します。このオブジェクトには、姿勢、ユーザー加速度(重力を除去したもの)、回転速度、較正済みの磁場が含まれます。
デバイスモーションに関するガイダンスでは、補正済み、磁北、または真北のフレームをハードコードせず、スニペット内で実行時にフレームを確認する方法を示してください。優先するフレームが利用できない場合は、.xArbitraryZVertical にフォールバックします。
guard 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()
姿勢の基準フレーム
単純な傾き操作には、.xArbitraryZVertical または .xArbitraryCorrectedZVertical を使用してください。これらのフレームは、磁力計や位置情報への依存を避けられます。補正済み、磁北、または真北のフレームをリクエストする前に、CMMotionManager.availableAttitudeReferenceFrames() を呼び出し、利用可能なフレームにフォールバックしてください。
| フレーム | 用途 |
|---|---|
.xArbitraryZVertical | デフォルトです。Z 軸は垂直で、X 軸は開始時に任意の方向を向きます。ほとんどのゲームに適しています。 |
.xArbitraryCorrectedZVertical | 上記と同じですが、時間の経過によるジャイロスコープのドリフトが補正されます。 |
.xMagneticNorthZVertical | X 軸が磁北を指します。磁力計が必要です。 |
.xTrueNorthZVertical | X 軸が真北を指します。磁力計と位置情報が必要です。 |
使用前に利用可能なフレームを確認します。
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
// Safe to use true north
}
CMPedometer:歩数と距離のデータ
CMPedometer は、歩数、距離、ペース、ケイデンス、階数を提供します。
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()
利用可能かどうかの確認
| メソッド | 確認する項目 |
|---|---|
isStepCountingAvailable() | 歩数計ハードウェア |
isDistanceAvailable() | 距離の推定 |
isFloorCountingAvailable() | 階数を計測する気圧高度計 |
isPaceAvailable() | ペースデータ |
isCadenceAvailable() | ケイデンスデータ |
CMMotionActivityManager:アクティビティ認識
ユーザーが静止中、歩行中、走行中、自転車走行中、または乗り物で移動中かを検出します。
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 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:高度データ
高度計へのアクセスは NSMotionUsageDescription の対象です。モーションへのアクセスが拒否された場合は、データが利用できない状態や更新ハンドラーのエラーを適切に処理してください。
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()
絶対高度とは海面を基準とした高度であり、GPS に基づく高度ではありません。まず利用可能かどうかを確認してください。絶対高度を利用できるのは、iPhone 12 以降や Apple Watch Series 6、Apple Watch SE 以降など、対応ハードウェアに限られます。
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()
更新間隔とバッテリー
| 間隔 | Hz | ユースケース | バッテリーへの影響 |
|---|---|---|---|
1.0 / 10.0 | 10 | UI の向き | 低 |
1.0 / 30.0 | 30 | カジュアルゲーム | 中 |
1.0 / 60.0 | 60 | アクションゲーム | 高 |
1.0 / 100.0 | 100 | 最大レート(iPhone) | 非常に高 |
必要な要件を満たす最低の頻度を使用してください。デバイス間で最大サンプルレートが固定されていると想定しないでください。高頻度のワークアウト動作には、対応している場合はCMBatchedSensorManagerを使用し、読み取り専用プロパティを設定するのではなく、報告されたaccelerometerDataFrequencyまたはdeviceMotionDataFrequencyを読み取ってください。
よくある間違い
やってはいけないこと:複数のCMMotionManagerインスタンスを作成する
アプリ全体で単一のCMMotionManagerを保持してください。複数のインスタンスが競合すると、更新レートが低下することがあります。
やってはいけないこと:センサーの利用可否を確認しない
各センサーストリームを開始する直前に、対応するis...Availableゲートを適用してください。
やってはいけないこと:更新の停止を忘れる
開始処理に対応する停止処理を、ライフサイクル上の対になる処理またはタスクのキャンセル経路に必ず組み合わせてください。
やってはいけないこと:不必要に高い更新レートを使う
インタラクションに必要な最低レートを選び、出発点として更新間隔とバッテリーの表を使用してください。
やってはいけないこと:すべてのCMMotionActivityプロパティが相互排他的だと想定する
// 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()
}
レビューチェックリスト
- Info.plistに、明確な説明を添えた
NSMotionUsageDescriptionがある - アプリ全体で単一の
CMMotionManagerインスタンスを共有している - 更新を開始する前にセンサーの利用可否を確認している(
isAccelerometerAvailableなど) - 歩数計/アクティビティAPIを使用する前に認証状態を確認している
- 更新間隔を許容できる最低の頻度に設定している
- すべての
start*Updates呼び出しに、ライフサイクル上の対になる処理で対応するstop*Updatesがある - ハンドラーが適切なキューにディスパッチされている(負荷の高い処理でメインスレッドをブロックしない)
- アクティビティの種類に基づいて処理する前に、
CMMotionActivity.confidenceを確認している - 更新ハンドラー内でエラーパラメーターを確認している
- デバイスモーションのコード例で、特定の姿勢フレームを要求する前に
CMMotionManager.availableAttitudeReferenceFrames()を呼び出している - 姿勢参照フレームが実際の必要性に基づいて選択されている(不必要に真北をデフォルトにしていない)
参考資料
- 発展的なパターン(SwiftUIとの統合、バッチセンサーマネージャー、ヘッドフォンのモーション、水中での浸水):references/motion-patterns.md
- CoreMotionフレームワーク
- CMMotionManager
- CMPedometer
- CMMotionActivityManager
- CMDeviceMotion
- CMAltimeter
- CMAbsoluteAltitudeData
- CMBatchedSensorManager
- CMHeadphoneMotionManager
- CMWaterSubmersionManager
- 浸水データへのアクセス
- 処理済みデバイスモーションデータの取得