Open3

HKStatisticsCollectionQuery について

Shun UematsuShun Uematsu

HKStatisticsCollectionQueryとは

HealthKitのAPIの一つであり、一定の間隔で複数の統計用のQueryを実行するQueryです。
HKStatisticsCollectionQueryはグラフやチャートのデータを生成するためによく使われます。
e.g. 1日ごとの合計歩数の取得、1時間毎の平均心拍数の取得 etc.

また新規のヘルスデータの作成や削除など、ヘルスデータの変更を監視できます。

Shun UematsuShun Uematsu

HKStatisticsCollectionQueryのイニシャライザ

HKStatisticsCollectionQueryのイニシャライザのシグネチャは以下のようになっています。

init(
  quantityType: HKQuantityType, 
  quantitySamplePredicate: NSPredicate?, 
  options: HKStatisticsOptions = [], 
  anchorDate: Date, 
  intervalComponents: DateComponents
)

個人的に分かりにくいと感じたのがanchorDateの引数です。
anchorDateの概念を考えるにあたって、時間を以下のような数直線と考えてください。

HKStatisticsCollectionQueryanchorDateintervalComponentsを用いて一致するサンプルデータ分割します。
anchorDateで任意の開始点を定義しますが、開始点はさほど重要ではないことが多いです。
なぜならTimeIntervalはanchor pointから両方向に広がるため、どこをanchor pointにしても結局はすべての時間が含まれるためです。

重要なのは

例として1日ごとの歩数を取得してみます。
1日ごとなのでTimerIntervalは1日ですが、アンカーポイントは1970年1月1日午前3時34分または2065年3月15日午前3時34分である可能性があります。どちらのアンカーポイントでもクエリは一致するサンプルデータを日単位で分割し、毎日午前3時34分を分割の始点とします。

Shun UematsuShun Uematsu

直近3ヶ月の1週間ごとの歩数の合計を取得してみる

まずはアンカーポイントと時間間隔を作成します。

let calendar = Calendar.current
// 1週間 == 7日間なので、間隔を7日で指定します。
let interval = DateComponents(day: 7)
// アンカーポイントの日付を月曜日の午前3時にしていします。
let components = DateComponents(
  calendar: calendar,
  timeZone: calendar.timeZone,
  hour: 3, 
  minute: 0,
  second: 0,
  weekday: 2
)
// アンカーポイントを直前の月曜午前3時にします。
 guard let anchorDate = calendar.nextDate(
  after: Date(),
  matching: components,
  matchingPolicy: .nextTime,
  repeatedTimePolicy: .first,
  direction: .backward
) else { fatalError() }

次に先程作成した要素を用いて、HKStatisticsCollectionQuery を作成します。

let query = HKStatisticsCollectionQuery(
  quantityType: .quantityType(forIdentifier: .stepCount)!,
  quantitySamplePredicate: nil,
  options: .cumulativeSum,
  anchorDate: anchorDate,
  intervalComponents: interval
)

クエリの実行結果を受け取るコールバックハンドラーを指定します。

query.initialResultsHandler = { query, collection, error in
  guard let statsCollection = collection else { return }
  let endDate = Date()
  let threeMonthAgo = DateComponents(month: -3)
  // 現時点から3ヶ月前のDateオブジェクトを取得。
  guard let startDate = Calendar.current.date(byAdding: threeMonthAgo, to: endDate) else { fatalError() }
  // enumerateStatisticsで指定した範囲の統計値を取得します。
  // 今回の場合、3ヶ月前から現時点までの統計値を取得します。
 // つまり直近から3ヶ月前の1週間毎の歩数の合計
  statsCollection.enumerateStatistics(from: startDate, to: endDate) { stats, stop in
    if let quantity = stats.sumQuantity() {
      let date = stats.startDate
      let value = quantity.doubleValue(for: .count())
      print("date: \(date), stepCount: \(value)")
    }
  }
}