💚

初めてのHealthKit

2021/05/03に公開
3

HealthKitの概要

HealthKitはiPhoneとAppleWatchによって収集されたヘルスデータ(心拍数や睡眠 etc.)とフィットネスデータ(ランニングや水泳 etc.)の読み書きを行うためのAPIを提供しています。

ヘルスケアデータやフィットネスデータはAppleWatchに保存されますが、AppleWatchとペアリングしているiPhoneが近くにある場合は、そのiPhoneにデータが自動的にバックアップされます。
またiCloudへのバックアップを有効にしている場合は、iCloudにもデータがバックアップされます。
以下のようなイメージです。

HealthKitで扱うデータについて

HeakthKitで扱うデータは主にHKSampleというオブジェクトで表しています。
HKSampleは以下のような要素で構成されています。

  • データタイプ(HKSampleType)
  • データの値(HKSampleのサブクラスによってプロパティは異なる)
  • 記録した時間(startDate, endDate)
  • データを作成したアプリやデバイスの情報(device)
  • メタデータ(metadata

HealthKitで扱うデータタイプは多種多様であり、それらはHKSampleのサブクラスとして表されています。次にその一例としてHKQuantitySampleについて見ていきます。

データを保存する

HealthKitで扱うデータについて知るために、試しに歩行距離を保存してみます。流れとしては以下のとおりです。

  1. 書き込み権限のリクエスト
  2. サンプルデータの作成
  3. HealthKitにデータを保存する

書き込み権限のリクエスト

// 1. アクセスしたいデータタイプを指定します。
let distanceType = HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning)

// 2. toShareには書き込みしたいデータタイプを指定しています。
healthStore.requestAuthorization(toShare: [distanceType], read: nil) { success, error in
    if success {
    } else {
    }
}
  1. HKObjectTypeに特定のデータタイプを表すオブジェクトを返すクラスメソッドが用意されています。 歩行距離は量を表すデータであるため、量を表すデータを書き込めるようにquantityType(forIdentifier:)を使用してHKQuantityTypeを取得します。。forIdentifierにはどのような量のデータがほしいかを指定します。今回は歩行距離のデータを書き込みたいためdistanceWalkingRunningを指定しています。
  2. healthStore.requestAuthorizationでヘルスデータへのアクセス権限をリクエストします。今回は歩行距離のデータのみを書き込みたいのでtoSharedistanceTypeを指定します。

歩行/走行距離のデータの作成と保存

次に歩行/走行の距離を保存してみます。

// 1. データのタイプを指定
let distanceType = HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning)!

// 2. データを記録した日時
let startDate = Calendar.current.date(bySettingHour: 14, minute: 35, second: 0, of: Date())!
let endDate = Calendar.current.date(bySettingHour: 15, minute: 0, second: 0, of: Date())!

// 3. 値
// HKQuantityは、値と単位を持っています。
let distanceQuantity = HKQuantity(unit: .meter(), doubleValue: 628.0)

// 4. 上記の要素を一つのデータとしてまとめる
let sample = HKQuantitySample(
    type: distanceType,
    quantity: distanceQuantity,
    start: startDate,
    end: endDate
)

// 5. データを保存
healthStore.save(sample) { success, error in
    if success {
    } else {
    }
}
  1. 保存したいデータのタイプを指定します。
    distanceWalkingRunningは歩行距離と走行距離のHKQuantityTypeです。
  2. データを記録した日時を指定します。
  3. HKQuantityは量に関するデータを表すオブジェクトです。
  4. HKQuantitySampleでこれまで用意したコンポーネントをまとめます。
  5. HKHealthStoreでクエリを実行し、データを保存します。クロージャには保存の成否がコールバックされます。
    これでサンプルデータの保存が完了しました。

今回は距離に関するデータを扱いたかったためHKQuantitySampleを使用しましたが、
当然HKQuantitySampleが向かないデータもあります。睡眠状態やワークアウトなどのデータです。
睡眠状態のデータは量で表すことができません。起きている、ベッドにいる、睡眠中、など定量的なデータではなく定質的なデータで表されます。
試しに睡眠状態も保存してみます。

睡眠状態のデータの作成と保存

定量的なデータタイプはHKQuantityTypeで表していましたが、定質的なデータはHKCategoryTtpeで表します。
また睡眠状態のデータは表すにはsleepAnalysisを用います。これらを用いると以下のような感じのデータを作成します。

時間 状態
00:00 ~ 01:00 ベッドいる
01:00 ~ 07:00 睡眠中
07:00 ~ 07:05 起床

以下は01:00~07:00まで睡眠中状態であることを保存する処理です。

// 1. データのタイプを指定
let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis)!

// 2. 開始/終了時刻
let startDate = Calendar.current.date(bySettingHour: 01:00, minute: 00, second: 0, of: Date())!
let endDate = Calendar.current.date(bySettingHour: 07:00, minute: 00, second: 0, of: Date())!

// 3. 上記の要素を一つのデータとしてまとめる
let sample = HKCategorySample(
    type: sleepType,
    value: HKCategoryValueSleepAnalysis.inBed.rawValue,
    start: startDate,
    end: endDate
)

// 4. 上記の要素を一つのデータとしてまとめる
self.healthStore.save(sample) { success, error in
    if success {
        print("success")
    } else {
        print("failed")
    }
}

基本的な流れは、歩行/走行距離を保存した時と同じですが、データを一つにまとめる際のクラスが異なります。睡眠状態は定質的なデータであるため、HKCategorySampleを使用し、value引数はIntを取り、ベッドにいる、睡眠中、起床のそれぞれの状態に対応しています。

このように扱うデータのタイプに応じて、Healthkitで用意されているものの中から適当なオブジェクトを選択するようにしてください。

ここまでのまとめ

ここまでHealthKitでデータを扱うためにまずはデータ構造について見てきました。
HealthKitで扱うデータで重要なことはデータのタイプです。具体的には、

  • 定量的なデータのタイプはHKQuantityType、定質的なデータのタイプはHKCategoryType
  • それに対応するデータはHKQuantitySampleHKCategorySample

のように表され、クラスの継承関係は以下のようになっています。

HKObjectType/HKObjectがルートクラスであり、データを作成したアプリやデバイスの情報やメタデータなどを持っています。
そしてそのサブクラスにHKSampleType/HKSampleがあり、これはデータを記録した日時などを持っています。
そして最後に具体的なデータを表すHKQuantityType/HKQuantitySampleHKCategoryType/HKCategorySampleなどがあり、それぞれに適したデータを持っている、というような階層になっています。

データの取得

データの作成/保存方法について見たので、次にデータの取得方法についても見ていきます。
HealthKitでヘルスデータを取得するには、クエリを使用します。
様々なクエリが用意されており、用途によって使い分けます。
ここでは、HKStatisticsQueryHKStatisticsCollectionQueryHKSampleQueryについて見ていきます。

まずはHKStatisticsQueryについて見ていきます。

1週間の歩行/走行距離の取得

名前の通りHKQuantitySampleの統計値を計算するクエリです。(統計値を計算するとは、平均値や最大値、最小値などを求めるということです。)
ではどのような統計値を計算できるか確認するために、例として1週間の歩数を取得してみます。

let distanceType = HKObjectType.quantityType(forIdentifier: .stepCount)!
let startDate = DateComponents(year: 2021, month: 6, day: 15)
let endDate = DateComponents(year: 2021, month: 6, day: 22)
let predicate = HKQuery.predicateForSamples(
    withStart: Calendar.current.date(from: startDate)!,
    end: Calendar.current.date(from: endDate)!
)
let query = HKStatisticsQuery(
    quantityType: distanceType,
    quantitySamplePredicate: predicate,
    options: [.cumulativeSum]) { query, statistics, error in
        print(statistics!.sumQuantity()!) // 6902 count
    }
healthStore.execute(query)

日毎の歩数を取得

先程は1週間の歩数の合計を取得しましたが、
日毎の歩数パターンを知りたい場合、日毎の歩数を取得しなければなりません。
まずは1週間の毎日の歩数を取得してみます。前述のコードの日付を変えるだけです。

let startDate = DateComponents(year: 2021, month: 6, day: 15)
let endDate = DateComponents(year: 2021, month: 6, day: 16)

ただ1週間なら良いですが、1年間の毎日の歩数を取得するとなると300個以上のHKStatisticsQueryを生成することとなり、対応しきれません。
解決方法はHKStatisticsCollectionQuery使用することです。
これは指定した期間の統計値を問い合わせるクエリです。

var calendar = Calendar.current
let stepCountType = HKObjectType.quantityType(forIdentifier: .stepCount)!

let startDate = DateComponents(year: 2021, month: 4, day: 23, hour: 0, minute: 0, second: 0)
let endDate = DateComponents(year: 2021, month: 4, day: 29, hour: 23, minute: 59, second: 59)

let predicate = HKQuery.predicateForSamples(
    withStart: calendar.date(from: startDate),
    end: calendar.date(from: endDate)
)

let query = HKStatisticsCollectionQuery(
    quantityType: stepCountType,
    quantitySamplePredicate: predicate,
    options: .cumulativeSum,
    anchorDate: Calendar.current.date(from: anchorDate)!,
    intervalComponents: DateComponents(day: 1)
)

// クエリの実行結果のコールバックハンドラーです
query.initialResultsHandler = { query, collection, error in
    collection?.enumerateStatistics(
        from: calendar.date(from: startDate)!,
	to: calendar.date(from: endDate)!
    ) { statistics, stop in
           print(statistics.sumQuantity() ?? "nil")
    }
}
    
healthStore.execute(query)

こうすることで2021/4/23〜2021/4/29までの各日の歩数を取得できます。
またHKStatisticsCollectionQueryではstatisticsUpdateHandlerを用いることで歩数の変更を監視することができます。

参考URL

GitHubで編集を提案

Discussion

MotokiMotoki

HealthKitを使用して歩数計アプリを作成しようとしているものです。
記事とても参考になりました。ありがとうございます。

1つお聞きしたいことがあります。
HealthKitがどの程度、過去のデータを取得できるのか?がわからないのでデータベースに保存するという方法が良いのかなと思っているのですが、もしご存知であれば過去のデータをどの程度取得できるのかをご教授頂けると助かります。
よろしくお願いいたします。

Shun UematsuShun Uematsu

どこまでのデータが取得可能か確認する一番手っ取り早い方法は、純正のヘルスケアアプリで参照できるデータを確認することです。ヘルスケアアプリで参照できるデータは全てHealthKitからでも取得可能です。

MotokiMotoki

コメントに返信ありがとうございます。
ヘルスケアアプリで参照できるデータに関しては取得可能ということですね。
分かりやすい回答ありがとうございます。