この記事はiOS #2 Advent Calendar 2019の6日目の記事です。
はじめに
Apple Watchで水泳のワークアウトが測定できるの知ってました?
ワークアウトアプリにプールスイミングがあるので、これを実行しながらプールで泳げば、測定できます。
測定結果はApple WatchとペアリングしているiPhoneのアクティビティアプリで確認できます。
サンプルデータですが、このように表示されます。
ラップ数や泳いでいるときの泳法も測定できていて、すごい! てなるのですが、
このアプリでは各ラップのストローク数は確認できません。
HealthKitで取れるだろう、と思ってやってみました。
アプリでHealthKitが使えるようにする
公式ドキュメントを参照して、手順通りに行います。
プロジェクトファイルとInfo.plistを編集します。
水泳のワークアウトデータを取ってみる
サンプルコード
import HealthKit
let healthStore = HKHealthStore()
let readDataTypes: Set<HKObjectType> = [HKWorkoutType.workoutType()]
healthStore.requestAuthorization(
toShare: nil,
read: readDataTypes,
completion: {(success, error) in
if success {
let predicate = HKQuery.predicateForWorkouts(with: HKWorkoutActivityType.swimming)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let sampleQuery = HKSampleQuery(
sampleType: HKObjectType.workoutType(),
predicate: predicate,
limit: HKObjectQueryNoLimit,
sortDescriptors: [sortDescriptor],
resultsHandler: {(_, results, error) in
if error == nil {
let workouts = results as! [HKWorkout]
if let workout = workouts.first {
print(workout.workoutEvents)
}
}
})
healthStore.execute(sampleQuery)
}
})
このコードをざっと説明すると、
- ヘルスケアデータからワークアウトを取る許可を取得する
- swimmingのワークアウトを最新から順に取得する
- 最新のワークアウトのworkoutEventsを出力する
になります。
print文で出力したworkoutEventsから、サンプルとして一つを抜き出してみると
HKWorkoutEventTypeLap, <_NSConcreteDateInterval: 0x28300eb40> (Start Date) 2019-10-24 10:56:05 +0000 + (Duration) 6.520985 seconds = (End Date) 2019-10-24 10:56:11 +0000 {
HKSwimmingStrokeStyle = 5;
水泳のワークアウトの1ラップのデータと読み取れますが、ストローク数と思われるデータはありません。
公式のドキュメントを参照すると分かるのですが、HKWorkoutEventクラスにはストローク数と思われるものはありません。
また、HKWorkoutクラスにtotalSwimmingStrokeCountというものはあるのですが、これはワークアウト単位のものであって、各ラップのストローク数ではありません。
別のアプローチが必要です。
そこで、ヘルスケアデータには水泳のストローク数というタイプがあるので、そこから取得しました。
水泳のストローク数から取得する
let readDataTypes: Set<HKObjectType> = [HKWorkoutType.workoutType(),
HKObjectType.quantityType(forIdentifier: .swimmingStrokeCount)!]
に書き換えて、先ほど取得したworkoutEventsを使って
let type = HKObjectType.quantityType(forIdentifier: .swimmingStrokeCount)!
for (index, workoutEvent) in workoutEvents.enumerated() {
// workoutEventのtypeにはlap, segment, pauseなどがあります
if workoutEvent.type == .lap {
let predicate = HKQuery.predicateForSamples(withStart: workoutEvent.dateInterval.start, end: workoutEvent.dateInterval.end, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: type,
quantitySamplePredicate: predicate,
options: [.cumulativeSum]) { (_, statistic, error) in
guard let statistic = statistic, error == nil else {
return
}
let sum = statistic.sumQuantity()?.doubleValue(for: HKUnit.count()) ?? 0
print("stroke:\(sum) index:\(index)")
}
healthStore.execute(query)
}
}
水泳のストローク数の保存データに対して、各workoutEventの開始日時と終了日時を指定して、クエリーを投げています。
print文の箇所は非同期に実行されるため、インデックスも追加して、workoutEventsの何番目か分かるようにしました。
stroke:8.0 index:1
stroke:3.0 index:2
stroke:15.0 index:3
stroke:11.0 index:4
stroke:11.0 index:5
stroke:6.0 index:6
stroke:11.0 index:7
stroke:10.0 index:9
stroke:7.0 index:10
stroke:13.0 index:12
stroke:7.0 index:15
stroke:10.0 index:13
stroke:4.0 index:16
stroke:11.0 index:14
出力はworkoutEventsの順番通りではありませんが、これでストローク数を取得できました。
HKStatisticsQueryの公式ドキュメント以外に参考になる事例を見つけられなかったので、
より良いやり方や、同じようなことを試みたことがあれば、コメント下さい。
サンプルコードはこちらにも上げました。
ありがとうございました。