CMPedometerを使うと簡単に歩数が測れます。歩数以外に、距離、速度も計測できるようです。
Xcode 11.3.1
CMPedometer
CMPedoMeter は各種センサーを扱うCore Motionにある歩数などの情報を取得することができます。
データは過去に遡って1週間程度まで取り出せるようですが、ハードがらみで割り込みはコストがかかるのか瞬時のデータは逆に出しにくくなっています。
プライバシー設定
ユーザーにプライバシー許可を取得しないといけません。(どんだけ歩いたかプライベートなことなんでしょうか…)
This app has crashed because it attempted to access privacy-sensitive data without a usage description….
こういうエラーが表示されたら、説明の通りNSMotionUsageDescriptionをInfo.plistに設定。
stringにはユーザーにこのアプリで使う理由を表示して許可を得られるようにします。
やり方としてはXcodeのInfo.plistに設定するか、直にファイルをエディタで開いて以下のように追加するといいでしょう。
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> ... <key>NSMotionUsageDescription</key> <string>to count your steps</string> ... </dict> </plist> |
startupdates
ライブのアップデートでデータを取得するためハンドラーを使いますが、UIで値を表示させたい時はメインスレッドで実行させないと遅延が生じます。
1 2 |
func startUpdates(from start: Date, withHandler handler: @escaping CMPedometerHandler) |
ViewController.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import UIKit import CoreMotion class ViewController: UIViewController { @IBOutlet var label:UILabel! let pedometer = CMPedometer() var results = "n/a" override func viewDidLoad() { super.viewDidLoad() // CMPedometerの確認 if(CMPedometer.isStepCountingAvailable()){ self.pedometer.startUpdates(from: NSDate() as Date) { (data: CMPedometerData?, error) -> Void in DispatchQueue.main.async(execute: { () -> Void in if(error == nil){ // 歩数 NSNumber? let steps = data!.numberOfSteps let results:String = String(format:"steps: %d", steps.intValue) self.label.text = results } }) } } } deinit { pedometer.stopUpdates() } } |
あとはストーリーボードにUILabelを置き、コードのlabelを紐付けします。
これで、実機にインストールして実際に歩いてみましょう
また、歩数以外にもいくつかのデータを取得できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// 距離 NSNumber? let distance = data!.distance!.doubleValue // 期間 let period = data!.endDate.timeIntervalSince(data!.startDate) // スピード let speed = distance / period // 平均ペース NSNumber? let averageActivePace = data!.averageActivePace // ペース NSNumber? let currentPace = data!.currentPace // リズム steps/second NSNumber? let currentCadence = data!.currentCadence // 昇ったフロアの数 NSNumber? let floorsAscended = data!.floorsAscended // 降りたフロアの数 NSNumber? let floorsDescended = data!.floorsDescended |
ただし、DispatchQueueを使ってメインスレッドでこれら全てのデータを一度に取得しようとするとデッドロックが生じる可能性があります。
Important
Attempting to synchronously execute a work item on the main queue results in deadlock.
DispatchQueue – Dispatch | Apple Developer Documentation
いずれにしても、データは非同期処理なので結果が出るのに多少時間がかかります。
queryPedometerData
アップデートとは別に時間を区切ってその期間の歩数を取得する方法もあります
1 2 |
func queryPedometerData(from start: Date, to end: Date, withHandler handler: @escaping CMPedometerHandler) |
このような感じになりました。
ViewController.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
import UIKit import CoreMotion class ViewController: UIViewController { @IBOutlet var label:UILabel! // class wide constant !! let pedometer = CMPedometer() var startDate = Date() var stopDate = Date() var pedArray = Array(repeating: "n/a", count: 9) override func viewDidLoad() { super.viewDidLoad() } @IBAction func buttonStart(_ sender : Any) { // let formatter = DateFormatter() // formatter.dateFormat = "yyyy-MM-dd' 'HH:mm:ss" startDate = Date() // print("\(formatter.string(from: startDate))") } @IBAction func buttonStopt(_ sender : Any) { // let formatter = DateFormatter() // formatter.dateFormat = "yyyy-MM-dd' 'HH:mm:ss" stopDate = Date() // print("\(formatter.string(from: stopDate))") if(CMPedometer.isStepCountingAvailable()){ self.pedometer.queryPedometerData(from: startDate, to: stopDate) { (data, error) in if(error == nil){ // 歩数 NSNumber? let steps = data!.numberOfSteps self.pedArray[0] = String(format:"steps: %d", steps.intValue) // 距離 NSNumber? let distance = data!.distance!.doubleValue self.pedArray[1] = String(format: "distance: %d", Int(distance)) // 期間 let period = data!.endDate.timeIntervalSince(data!.startDate) self.pedArray[8] = String(format: "period: %f", period) print("\(self.pedArray[8])") // スピード let speed = distance / period self.pedArray[2] = String(format: "speed: %f", speed) // 平均ペース NSNumber? if(data!.currentPace != nil){ let averageActivePace = data!.averageActivePace self.pedArray[3] = String(format: "averageActivePace: %f", averageActivePace!.doubleValue) } // ペース NSNumber? if(data!.currentPace != nil){ let currentPace = data!.currentPace self.pedArray[4] = String(format: "currentPace: %f", currentPace!.doubleValue) } // リズム steps/second NSNumber? if(data!.currentCadence != nil){ let currentCadence = data!.currentCadence self.pedArray[5] = String(format: "currentCadence: %f", currentCadence!.doubleValue) } // 昇ったフロアの数 NSNumber? let floorsAscended = data!.floorsAscended self.pedArray[6] = String(format: "floorsAscended: %d", floorsAscended!.intValue) // 降りたフロアの数 NSNumber? let floorsDescended = data!.floorsDescended self.pedArray[7] = String(format: "floorsDescended: %d", floorsDescended!.intValue) } } } var results: String = "PedoMeter" for data in pedArray { results += data results += "\n" } label.text = results } } |
これで、Labelは行数を10行程度にしてstartとstopのボタンをstoryboardに紐づければ完成です。
References:
CMPedometer – Core Motion | Apple Developer DocumentationCore Motion | Apple Developer Documentation
CMPedometerData
DispatchQueue – Dispatch | Apple Developer Documentation