Core Motionに含まれるPedometerの挙動の確認をしたので、確認に使ったサンプルアプリと確認の結果をご紹介します。
Pedometerのサンプルアプリの作成手順
作成したアプリは下記の画面を持ちます。(画面はApple Watchですが、iOSでも動くようなプログラムを書いていきます。)

STOPPEDと表記されているところは、今歩いているか止まっているかを示しています。
296は現在の歩数です。
一番下のボタンは、スタート/ストップボタンです。Pedometerのスタートとストップに使います。
権限設定
PedometerはCore Motionに含まれます。Core Motionを使う時にはInfo.plistで、「NSMotionUsageDescription」をキーとして、バリューに使用目的を書きます。
画面の作成
struct ContentView: View {
@ObservedObject var pedometer = MyPedometer()
var body: some View {
VStack {
if self.pedometer.isWalking {
Text("WALKING")
} else {
Text("STOPPED")
}
Text("\(self.pedometer.count)")
Button(action: {
if !self.pedometer.isStarted {
self.pedometer.start()
} else {
self.pedometer.stop()
}
}) {
if !self.pedometer.isStarted {
Text("スタート")
} else {
Text("ストップ")
}
}
}
}
}
MyPedometerで歩数をカウントします。
歩数を数える
歩数を数えるMyPedometerクラスを作ります。
class MyPedometer: NSObject, ObservableObject {
@Published var isStarted = false
@Published var isWalking = false
@Published var count = 0
let pedometer = CMPedometer()
func start() {
guard !isStarted else {
return
}
isStarted = true
pedometer.startEventUpdates { (event, error) in
guard error == nil else {
print("error: \(String(describing: error))")
return
}
DispatchQueue.main.async {
if event!.type == CMPedometerEventType.pause {
self.isWalking = false
} else {
self.isWalking = true
}
}
}
pedometer.startUpdates(from: Date()) { (data, error) in
guard error == nil else {
print("error \(String(describing: error))")
return
}
DispatchQueue.main.async {
self.count = data?.numberOfSteps as! Int
}
}
}
func stop() {
guard isStarted else {
return
}
isStarted = false
pedometer.stopUpdates()
pedometer.stopEventUpdates()
}
}
順番に説明します。
まずはメンバから。
@Published var isStarted = false
@Published var isWalking = false
@Published var count = 0
let pedometer = CMPedometer()
isStartedに、Pedometerで測定中かどうか格納します。
isWalkingは、Pedometerからアクティビティの停止と再開イベントを受けることができるので、それを使ってウォーキング中なのかそうでないかを判断し格納します。
countは歩数です。
pedometerに、Core MotionのCMPedometer()をインスタンス化して保持しておきます。
次に、歩数のカウントの開始をするstart()メソッドです。
func start() {
guard !isStarted else {
return
}
isStarted = true
pedometer.startEventUpdates { (event, error) in
guard error == nil else {
print("error: \(String(describing: error))")
return
}
DispatchQueue.main.async {
if event!.type == CMPedometerEventType.pause {
self.isWalking = false
} else {
self.isWalking = true
}
}
}
pedometer.startUpdates(from: Date()) { (data, error) in
guard error == nil else {
print("error \(String(describing: error))")
return
}
DispatchQueue.main.async {
self.count = data?.numberOfSteps as! Int
}
}
}
pedometer.startEventUpdate()で、アクティビティを停止したか再開したかを通知するようにします。
pedometer.startUpdates()で、歩数のカウントを開始します。1つ目の引数で、開始時刻を指定できます。今回はスタートボタンを押した時から始めたかったので、現在時刻を入れました。
次にstop()メソッドです。
func stop() {
guard isStarted else {
return
}
isStarted = false
pedometer.stopUpdates()
pedometer.stopEventUpdates()
}
フラグの更新と、通知の停止をしているだけです。
以上でサンプルコードの説明は終わりです。ソースコード全体はGitHubに置いておきましたので、ご自由に利用してください。プロジェクトはApple Watch用ですが、MyPedometer.swiftとContentView.swiftをコピーすればiOSでもそのまま動きます。
Pedometerの挙動について
下記の挙動を確認したくてこのサンプルアプリを作りました。興味のある方だけお読みください。
- 歩数の通知の頻度はどの程度なのか?
- アクティビティの停止と再開の応答性はどの程度なのか?
まず、歩数の通知の頻度については、1歩1歩通知が来るのではなくて、1秒に1回くらいで通知が来ていました。また、歩き始めてから通知が来るまではけっこうタイムラグがあって、普通に歩いてると10〜20歩くらい歩いてから通知がき始めました。最初の数歩は、本当に歩いているのかどうかをチェックしているのかもしれませんね。
また、アクティビティの停止と再開の応答性についてですが、再開(開始)は、歩数の通知と同様で10〜20歩ほど歩いてからの通知となりました。ただ、歩数の通知が始まっても、しばらく再開の通知がこないこともありました。歩数計アプリとしては、この辺りの厳密性は不要という考え方なのでしょう。停止については1〜2秒程度ですぐに通知が来ました。
このアプリをiOSとwatchOSの両方で試してみましたが、どちらも同じ動きでした。
終わりに
本記事ではPedometerを使ったサンプルアプリを紹介しました。元々、確認したいことがあって作ったものなので、詳しい説明はしていませんが、誰かの参考になれば幸いです。
ちなみに、Apple Watchで歩数計アプリを作るのであれば、HealthKitを使うのが本筋だと思います。