HealthKitで水泳の各ラップのストローク数が取りたい

この記事は[iOS #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ios-2)の6日目の記事です。

## はじめに
Apple Watchで水泳のワークアウトが測定できるの知ってました？
![IMG_4193.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103548/c079d85f-acd1-e22e-bbb0-e83b39827933.png)

ワークアウトアプリにプールスイミングがあるので、これを実行しながらプールで泳げば、測定できます。
測定結果はApple WatchとペアリングしているiPhoneのアクティビティアプリで確認できます。
サンプルデータですが、このように表示されます。
![IMG_4195.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103548/de1cdb70-b1f0-2058-7050-77ce347072e7.jpeg)

ラップ数や泳いでいるときの泳法も測定できていて、すごい！ てなるのですが、
このアプリでは各ラップのストローク数は確認できません。
HealthKitで取れるだろう、と思ってやってみました。

# アプリでHealthKitが使えるようにする
[公式ドキュメント](https://developer.apple.com/documentation/healthkit/setting_up_healthkit)を参照して、手順通りに行います。
プロジェクトファイルとInfo.plistを編集します。

# 水泳のワークアウトデータを取ってみる
サンプルコード

```.swift
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)
        }
})
```

このコードをざっと説明すると、
1. ヘルスケアデータからワークアウトを取る許可を取得する
2. swimmingのワークアウトを最新から順に取得する
3. 最新のワークアウトの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;
```

水泳のワークアウトの１ラップのデータと読み取れますが、ストローク数と思われるデータはありません。
[公式のドキュメント](https://developer.apple.com/documentation/healthkit/hkworkoutevent)を参照すると分かるのですが、HKWorkoutEventクラスにはストローク数と思われるものはありません。
また、HKWorkoutクラスに[totalSwimmingStrokeCount](https://developer.apple.com/documentation/healthkit/hkworkout/2341561-totalswimmingstrokecount)というものはあるのですが、これはワークアウト単位のものであって、各ラップのストローク数ではありません。
別のアプローチが必要です。

そこで、ヘルスケアデータには水泳のストローク数というタイプがあるので、そこから取得しました。

# 水泳のストローク数から取得する

```.swift
let readDataTypes: Set<HKObjectType> = [HKWorkoutType.workoutType(),
                                        HKObjectType.quantityType(forIdentifier: .swimmingStrokeCount)!]
```

に書き換えて、先ほど取得したworkoutEventsを使って

```.swift
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の公式ドキュメント](https://developer.apple.com/documentation/healthkit/hkstatisticsquery)以外に参考になる事例を見つけられなかったので、
より良いやり方や、同じようなことを試みたことがあれば、コメント下さい。

サンプルコードは[こちら](https://gist.github.com/ochim/981fa55dd42cc0bc2a3a35a85d23311c)にも上げました。

ありがとうございました。