はじめに
行動認識とか,ライフログとか大学(+院)の学生時代に興味持っていて
Core Motion についても調べ一度ブログにまとめたことがありました。
Objective-C(今見るとツッコミどこが満載) だったので
勉強がてら Swift 3 版つくるかということで再度作り直しました。
ブログには iOS 側について,Qiita には試しに Apple Watch に
表示させた部分について書きたいと思います。
一部,Shinagawa.swift #2 で発表した内容になります。
Core Motion とは
簡単にいえばデバイスのモーションデータを取得できる
Frameworkで,得られる色々なデータを適切に用いることで
より良いユーザ体験を提供できる。
これらのデータは目に見えないモノなので可視化して
ユーザに見える形になってようやく意味をなすという感じでしょうか。
**補足**
iPhone 5s からモーションコプロセッサ(M7)が搭載され, より省電力化されています。Core Motion で取得可能な主なデータ
- CMAccelerometerData - 加速度関係
- CMAltitudeData - 相対高度。気圧関係
- CMGyroData - ジャイロ関係
- CMMagnetometerData - 磁気関係
- CMMotionActivity - 動作状態関係
- CMPedometerData - 歩数・距離関係
- CMPedometerEvent - 一時停止・再開関係
などなどでかなり多岐に渡ります。
それぞれ取得できる iOS/watchOS のバージョン,
端末があるので注意しながら実装していく必要があります。
(例として,気圧などは iPhone 6 以降の端末で取得可能)
今回の実装イメージ
今回は歩数を扱う CMPedometerData と
iOS 10から追加された CMPedometerEvent と
ユーザの動作情報が取得できる CMMotionActivity を
せっかくなので Apple Watch で取得し,
今日歩いた歩数と歩数計イベント(停止・再開),
動作状態(静止・歩行・走行・交通機関・自転車)とどのくらい信頼度あるか を
画像やラベルで可視化してみます!
絵をもっと上手くやればもっとそれっぽくなると思います・・・
watch App の導入の仕方については説明を省きます。
これくらいの情報なら Apple Watch で閲覧できれば十分なのかなと思います。
**補足**
CMPedometerData は iOS 8 以上,watchOS 2以上です。 (iOS 7 では CMStepCounter というものでした) CMPedometerEvent は iOS 10 以上,watchOS 3 以上です。 CMMotionActivity は iOS 7 以上,watchOS 2 以上です。今回のサンプルコード
iOS: Core Motion 全般(一部除く) ←ブログで記事書きます
watchOS: 歩数とイベントタイプ・アクティビティ ← 本記事
GitHub: CoreMotioner
ブログ: 後日
動作環境等
・Swift 3
・iOS 10 以上
・watchOS 3 以上
・Xcode 8.2 で動作確認
CoreMotion 導入
簡単
1. CoreMotion.framework を追加
TARGETS -> Build Phases -> Link Binary With Libraries
2. Info.plist に使用目的等を記述(iOS 10 で必須に)
TARGETS -> Info
あとは使いたい ファイルで import CoreMotion
を追加すれば良いです。
実装 1 (今日歩いた歩数とイベントタイプ)
1. インスタンスを生成
// インスタンスを生成
let pedometer: CMPedometer = CMPedometer()
2. 利用できるかを確認
Watch の場合,取得関数を呼ぶときは willActive 内に書く。
画面消えたら停止処理を書く didDeactivate が呼ばれるため。
// 歩数取得可能 or not
if CMPedometer.isStepCountingAvailable() {
self.acquirePedometerData()
}
// PedometerEvent取得可能 or not
if CMPedometer.isPedometerEventTrackingAvailable() {
self.displayEventType()
}
**補足**
他のデータ取得に関してもだいたい用意されています。 一応書かなくても取得できない場合, 各取得データは nil になるので guard ではじけます。 それぞれ確認できないと呼べないという場合の書き方が いまいちわからないです・・・(iOS 版で)3-A. 今日歩いた歩数のデータを取得
歩数取得のメソッドは下記のようになります。
今日の 0 時を計算して,0 時を起点に
今日の歩数を取得してラベルに表示させています。
非同期なのでリアルタイムでの更新はされないです。
だいたい 4 ~ 5歩くらいで更新されます。
// 歩数表示のラベル
@IBOutlet var stepCountsLabel: WKInterfaceLabel!
let pedometer: CMPedometer = CMPedometer()
// CMPedometerData(歩数・距離)
var numberOfSteps: NSNumber = 0
...
func acquirePedometerData() {
let now = Date() // 現在時間
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents([.year, .month, .day], from: now)
let today = calendar.date(from: components) // 今日の0時
self.pedometer.startUpdates(from: today!, withHandler: {
(data: CMPedometerData?, error: Error?) in
DispatchQueue.main.async(execute: { () in
guard let exData = data, error == nil else {
// 取得できなかった場合ここで抜ける
return
}
// 歩数取得
self.numberOfSteps = exData.numberOfSteps
self.stepCountsLabel.setText(self.numberOfSteps.description)
})
})
}
**補足**
今回は,起点指定の ```startUpdates(from:withHandler:)``` を用いましたが, 起点と終点を指定できるものもあります。 ```queryPedometerData(fromwithHandler:)``` こちらは一度だけ呼ばれ一度だけ更新されます。 iOS 版では(ちょうど1週間分歩数・距離取得などで)実装しているので よかったらご覧ください。3-B. イベントタイプの取得(一時停止・再開)
EventType 取得メソッドは下記のようになります。
値は 2 つだけでそれぞれに該当する画像を表示させています。
@IBOutlet var eventTypeImageView: WKInterfaceImage!
// CMPedometerEvent(歩行の一時停止・再開)
var pedometerEventType = ""
...
func displayEventType() {
self.pedometer.startEventUpdates(handler: {
(event: CMPedometerEvent?, error: Error?) in
DispatchQueue.main.async(execute: { () in
guard let exEvent = event, error == nil else {
return
}
switch exEvent.type {
case .pause:
self.pedometerEventType = "PAUSE"
self.eventTypeImageView.setImageNamed("pause")
case .resume:
self.pedometerEventType = "RESUME"
self.eventTypeImageView.setImageNamed("resume")
})
})
}
**補足**
[CMPedometerEvent](https://developer.apple.com/reference/coremotion/cmpedometerevent) は iOS 10 から追加された。 (No overview available. で今のところ説明がない) [CMPedometerEventType](CMPedometerEventType) は ```.resume``` と ```.pause``` の2種類。 使ってみた感じタイミング的には結構正確なので だるまさんが転んだゲームとかすぐ作れそう。4. 取得終了メソッドを呼ぶ
スタートしたらストップもする。
画面消えたら止まっちゃいます。
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
// 計測終了
self.pedometer.stopUpdates()
self.pedometer.stopEventUpdates()
}
実行結果 (今日歩いた歩数とイベントタイプ)
- 静止時
- 動いたとき
走っても,歩いても同じ挙動でした。
変更が多いのか画像がチラチラすることもありました。
実装 2 (動作状態を表示)
1. インスタンスを生成
// インスタンスを生成
let activityManager = CMMotionActivityManager()
2. 利用できるかを確認
// アクティビティ取得可能だったら取得する
if CMMotionActivityManager.isActivityAvailable() {
self.displayActivityData()
}
3. アクティビティデータを取得・表示
アクティビティデータ取得のメソッドは下記になります。
信頼度(confidence)は .low, .medium, .high
で
状態は5種類(stationary,walking,running, automotive, cycling)で
それぞれ Bool 値で取得可能です。
func displayActivityData() {
self.activityManager.startActivityUpdates(to: OperationQueue.current!,
withHandler: {(data: CMMotionActivity?) in
DispatchQueue.main.async(execute: { () in
guard let exData = data else {
return
}
// ユーザの状態(Bool)
self.stationary = exData.stationary
self.walking = exData.walking
self.running = exData.running
self.automotive = exData.automotive
self.cycling = exData.cycling
// confidence は CMMotionActivityConfidence 型
// ユーザの状態の信頼度(精度)
switch exData.confidence {
case .low:
self.confidence = "Low"
case .medium:
self.confidence = "Medium"
case .high:
self.confidence = "High"
}
self.confidenceLabel.setText(self.confidence)
// 静止・歩行・走行が複数trueにはならないと思われ・・・
if self.stationary {
self.mainActivityImage.setImageNamed("stationary")
} else if self.walking {
self.mainActivityImage.setImageNamed("walking")
} else if self.running {
self.mainActivityImage.setImageNamed("running")
}
// 交通機関で移動・自転車で移動も同時にtrueとはならないと思われ・・・
if self.automotive {
self.subActivityImage.setImageNamed("automotive")
} else if self.cycling {
self.subActivityImage.setImageNamed("cycling")
}
})
})
}
それぞれの Bool 値は独立なので,この書き方は問題あるかもしれないです。
例えば,交通機関に乗っているときに静止も YES になることもあるはず。
4. 取得終了メソッドを呼ぶ
こちらもスタートさせたらストップさせる。
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
// 停止処理
self.activityManager.stopActivityUpdates()
}
実行結果 (動作状態を表示)
あとはまだ試してないです。
交通機関は iOS 版は YES(true) になることがありましたが,
Apple Watch だと静止だけの状態になっていました。
手首に Apple Watch を固定しているからかなーと。
例えば悪路をラリーカーなどで走れば変わってくるかもしれません。
おわりに
Core Motion で取得できるデータは多い(取得して何に使うかが重要)。
iOS デバイスだけでなく Apple Watch でも取れる。
計算系は苦手かもしれないけど常に装着しているものだしデータ取得や
簡単なデータの閲覧には Apple Watch は向いていると思う。
ユーザとしてカラダはもちろんのこと,
iOS アプリエンジニアとしては,頭や手を動かして
Apple Watch もっと使ってみたいです。
乱文でしたが,ご覧いただきありがとうございました!!
参考
- Core Motion API Reference
-
CoreMotion で色々なデータを取得してみた(基礎)
↑学生時代に書いた色々つっこみたくなる記事