📱

[Swift]CoreMotionを使用してデバイス(iPhone)が動いたことを検知してみる

2023/04/20に公開

概要

CoreMotionを使用すると加速度センサーやジャイロセンサー、歩数計に磁力計に気圧計・・・などデバイスに搭載されているセンサーの値を取れたりします。
今回はこのCoreMotionを使用してiPhoneが動いたことを検知する方法を紹介したいと思います。
(見づらいですが使用している様子です)

環境

この記事は以下のバージョン環境のもと作成されたものです。
【Xcode】14.3
【iOS】16.4
【macOS】Ventuta

CoreMotion

CoreMotionの概要については冒頭に書きましたのでもっと詳細を知りたい方は以下のドキュメントを参照ください。
https://developer.apple.com/documentation/coremotion

CoreMotionはiOS4.0〜使用可能です。
プライバシーに関するモーション(例えば歩数計でウォーキング、ランニング、運転などモーションタイプなど)を取得する際にはinfo.plistより許諾を取る為の処理が必要です。
Privacy - Motion Usage Description

今回はCMMotionManagerを使用して検知を行いたいと思います。CMMotionManagerから取得できる情報はプライバシー関連とは見なされないため、許諾を得ずに使用する事ができます。
余談ですがCMMotionManagerは4種類のモーションデータを受け取る事ができます。
https://developer.apple.com/documentation/coremotion/cmmotionmanager

CMMotionManagerを使用してみる

CMMotionManagerを使用する際には必ずフレームワークをImportする必要があります。
import CoreMotion

続いて書くセンサーが使用できるか確認する必要があります。
https://developer.apple.com/documentation/coremotion/cmmotionmanager#2870651

今回はデバイスの動きを取得したいのでisDeviceMotionAvailableを使用します。

またCMDeviceMotionを使用する事でデバイスの姿勢や角度、ベクトルや加速度など取得する事ができます。
取得できる値は以下のドキュメントの通りです。
https://developer.apple.com/documentation/coremotion/cmdevicemotion

今回は加速度(userAcceleration)をトリガーに、iPhoneが動いたことを検知したいと思います。

コード

MotionManager
final class MotionManager: ObservableObject {
    @Published private(set) var isTheftProtectionMode = false
    @Published private(set) var isVibrationActive = false

    private let motionManager = CMMotionManager()
    private var vibrationTimer: Timer?

    func toggleTheftProtectionMode() {
        isTheftProtectionModeOn.toggle()
        if isTheftProtectionModeOn {
            startMonitoringDeviceMotion()
        } else {
            stopMonitoringDeviceMotion()
        }
    }

    private func startMonitoringDeviceMotion() {
        if motionManager.isDeviceMotionAvailable {
            motionManager.deviceMotionUpdateInterval = 0.5

            motionManager.startDeviceMotionUpdates(to: .main) { [weak self] (data, error) in
                guard let data = data else { return }
                let threshold: Double = 0.1
                let userAcceleration = data.userAcceleration
                if abs(userAcceleration.x) > threshold || abs(userAcceleration.y) > threshold || abs(userAcceleration.z) > threshold {
                    self?.startVibration()
                }
            }
        }
    }

    private func stopMonitoringDeviceMotion() {
        if motionManager.isDeviceMotionActive {
            motionManager.stopDeviceMotionUpdates()
            stopVibration()
        }
    }

    private func startVibration() {
        isVibrationActive = true
        vibrationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            if self.isVibrationActive {
                AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
            } else {
                self.vibrationTimer?.invalidate()
                self.vibrationTimer = nil
            }
        }
    }

    private func stopVibration() {
        isVibrationActive = false
    }
}

motionManager.deviceMotionUpdateInterval = 0.5 では更新時間の間隔を定義してます。
let threshold: Double = 0.1 では加速度の閾値を設定しています。この値が小さいほど、少しの動きでも反応するようになります。
あとのコードは検知した時にバイブレーションを起動したり、繰り返したり、止めたりしているコードになります。この辺りは https://zenn.dev/articles/421132da5a06bb/edit を参考にしてください。

あとは適当に画面を作れば終了です。

ContentView
struct ContentView: View {
    @StateObject private var motionManager = MotionManager()
    var body: some View {
        VStack {
            Text(motionManager.isVibrationActive ? "バイブレーション:オン" : "バイブレーション:オフ")

            Button {
                motionManager.toggleTheftProtectionMode()
            } label: {
                Text(motionManager.isTheftProtectionMode ? "盗難防止モードオン" : "盗難防止モードオフ")
                    .padding()
                    .background(motionManager.isTheftProtectionMode ? Color.red : Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
    }
}

完成するとこんな感じの画面になり、盗難防止モードオン中にデバイスを動かそうとするとバイブレーションがオンになります。

まとめ

CoreMotionを使用するとデバイスのセンサーから情報を取得できるのは感動しました。
とりあえず検証してみただけなのでerrorなどのハンドリングはしてませんが、実装する場合は適時していただければと思います。
以上CoreMotionを使用してiPhoneが動いたことを検知してみるでした。

Discussion