AirPodsの首振り・頷きジェスチャーを検知するiOSアプリ
はじめに
AirPods Pro、AirPods(第3世代)、AirPods Maxには、モーションセンサーが内蔵されています。このセンサーを使うと、ユーザーの頭の動き(頷き・首振り)を検知することができます。
本記事では、AppleのCMHeadphoneMotionManagerを使って、首振りジェスチャーを検知する方法を解説します。
当記事のサンプルコードはこちらです。
対応デバイス
以下のAirPodsが対応しています。
- AirPods Pro(第1世代・第2世代)
- AirPods(第3世代)
- AirPods Max
注意: AirPods(第1世代・第2世代)は非対応です。

事前準備
Info.plistの設定
モーションセンサーにアクセスするには、Info.plist に使用目的の説明を追加する必要があります。
<key>NSMotionUsageDescription</key>
<string>AirPodsのジェスチャーを検知するために使用します</string>
ユーザー側の設定
アプリを使用する前に、以下の設定が必要です。
- AirPodsをiPhoneに接続する
- 設定 > AirPods(自分のAirPodsの名前) > 頭のジェスチャー をONにする
- アプリ初回起動時にモーションアクセスを許可する
実装の解説
CMHeadphoneMotionManagerの初期化
まず、CoreMotionフレームワークをインポートし、CMHeadphoneMotionManagerのインスタンスを作成します。
import CoreMotion
class HeadphoneMotionManager {
private let motionManager = CMHeadphoneMotionManager()
var isAvailable: Bool {
return motionManager.isDeviceMotionAvailable
}
}
isDeviceMotionAvailableプロパティで、対応したAirPodsが接続されているかを確認できます。
モーションデータの取得開始
startDeviceMotionUpdatesメソッドを呼び出すと、AirPodsからリアルタイムでモーションデータを受信できます。
func startUpdates() {
guard motionManager.isDeviceMotionAvailable else {
print("Headphone motion is not available")
return
}
motionManager.startDeviceMotionUpdates(to: .main) { [weak self] motion, error in
guard let self = self, let motion = motion else { return }
self.processMotion(motion)
}
}
コールバックで受け取るCMDeviceMotionオブジェクトには、AirPodsの姿勢情報が含まれています。
姿勢データ(attitude)の理解
CMDeviceMotionのattitudeプロパティから、頭の傾きを取得できます。
let currentPitch = motion.attitude.pitch // 上下の傾き(頷き)
let currentYaw = motion.attitude.yaw // 左右の回転(首振り)
| プロパティ | 軸 | 動作 | 正の値 | 負の値 |
|---|---|---|---|---|
| pitch | X軸 | 頷き | 上を向く | 下を向く |
| yaw | Z軸 | 首振り | 左を向く | 右を向く |
値の単位はラジアンです。
ジェスチャー検知のロジック
ジェスチャーを検知するには、前フレームとの差分を計算し、閾値を超えたかどうかを判定します。
// 閾値の設定(ラジアン/フレーム)
private let pitchThreshold: Double = 0.04 // 約2.3度
private let yawThreshold: Double = 0.04 // 約2.3度
// 前回の値を保持
private var previousPitch: Double?
private var previousYaw: Double?
private func processMotion(_ motion: CMDeviceMotion) {
let currentPitch = motion.attitude.pitch
let currentYaw = motion.attitude.yaw
defer {
previousPitch = currentPitch
previousYaw = currentYaw
}
guard let prevPitch = previousPitch, let prevYaw = previousYaw else {
return
}
let pitchDelta = currentPitch - prevPitch
let yawDelta = currentYaw - prevYaw
// 上下の検知(pitchの変化)
if abs(pitchDelta) > pitchThreshold {
let gesture: HeadGesture = pitchDelta > 0 ? .up : .down
notifyGesture(gesture)
return
}
// 左右の検知(yawの変化)
if abs(yawDelta) > yawThreshold {
let gesture: HeadGesture = yawDelta > 0 ? .left : .right
notifyGesture(gesture)
return
}
}
連続検知の防止
1回の首振りが複数回検知されるのを防ぐため、クールダウン期間を設けます。
private var lastGestureTime: Date = Date.distantPast
private let cooldownInterval: TimeInterval = 0.5
private func notifyGesture(_ gesture: HeadGesture) {
// クールダウン中は検知しない
guard Date().timeIntervalSince(lastGestureTime) > cooldownInterval else {
return
}
lastGestureTime = Date()
delegate?.headphoneMotionManager(self, didDetect: gesture)
}
この例では0.5秒のクールダウンを設定しています。
閾値のチューニング
閾値の調整は、ユーザー体験に大きく影響します。
| 閾値 | 感度 | 特徴 |
|---|---|---|
| 0.02 | 高い | 軽い動きでも反応。誤検知が増える可能性あり |
| 0.04 | 中程度 | バランスが良い。推奨値 |
| 0.08 | 低い | はっきりした動きのみ検知 |
実際のデバイスでテストしながら、用途に合った値を見つけてください。
私が検証してみた時は、0.08だとかなり大きくジェスチャーする必要がありました。
実用的には0.04程度が良いかと思います。
実機検証
初回起動時にモーションとアクティビティへのアクセス許可を求められます。

実際にAirPodsを装着し、左右、上下に首を振ると検知します。

まとめ
CMHeadphoneMotionManagerを使うことで、AirPodsのモーションセンサーから頭の動きを取得し、ジェスチャーとして活用できます。
ポイントをまとめると以下の通りです。
-
CMHeadphoneMotionManagerでAirPodsのモーションデータを取得 -
attitude.pitchで上下、attitude.yawで左右の動きを検知 - 前フレームとの差分で動きの方向と大きさを判定
- 閾値とクールダウンで誤検知を防止
ハンズフリー操作やアクセシビリティ機能など、さまざまな応用が考えられます。ぜひ試してみてください。
参考リンク
Discussion