🩺

【HealthKit】心拍数データを取得:最高・最低・平均・最新の値を確認する方法

2024/06/03に公開

HealthKitを用いて心拍数データを取得する

この記事では、HealthKitを使用して、iOSデバイスから心拍数データを取得し、最高・最低・平均・最新の心拍数を取得する方法を紹介します。

https://zenn.dev/muranaka/articles/8d58f0065f19a7

HKStatisticsCollectionQueryDescriptorを使用

HKStatisticsCollectionQueryDescriptorのデータ取得

HKStatisticsCollectionQueryDescriptorとは、HealthKitで用いるデータ取得のためのクエリの一種です。データ取得をしたい開始時点と終了時点を一定のインターバルで区切りデータを取得し、インターバル毎の合計値・平均値・最大値・最小値を算出します。例えば過去一週間を1日毎に区切り、歩数の合計値を取得したい場合に使用します。

実装

今回は心拍数データを使用します。過去一週間を1日毎に区切り、1日毎の心拍数の平均値・最大値・最小値・最新(1日毎の最後のサンプルデータ)の値を取得します。
それでは、実際のコードを交えて説明いたします。

カレンダーと日付の設定

let calendar = Calendar(identifier: .gregorian)
let endDate = calendar.startOfDay(for: Date())
guard let startDate = calendar.date(byAdding: .day, value: -6, to: endDate) else {
    fatalError("開始日時の計算に失敗しました")
}

ここでは、グレゴリオ暦のカレンダーを作成し、データ取得終了時点を実行した日のAM0:00をendDateとします。その後、開始日を計算するためにendDateから6日前の日付を計算します。

データ取得のための条件を作成

let thisWeek = HKQuery.predicateForSamples(withStart: startDate, end: endDate)
let heartRateType = HKQuantityType(.heartRate)
let heartRatesThisWeek = HKSamplePredicate.quantitySample(type: heartRateType, predicate: thisWeek)
  • 指定した期間の心拍数データを取得するための条件(NSPredicate)の定義します。
  • 取得したいデータタイプである心拍数(HKQuantityType)を指定します。

上記の2点の条件を用いて、HKSamplePredicate<HKQuantitySample>であるheartRatesThisWeekを宣言します。

HKStatisticsCollectionQueryDescriptorの設定

let heartRateQuery = HKStatisticsCollectionQueryDescriptor(
    predicate: heartRatesThisWeek,
    options: [.mostRecent ,.discreteAverage ,.discreteMax ,.discreteMin ,.separateBySource],
    anchorDate: endDate,
    intervalComponents: everyDay
)

心拍数のデータを取得するためのクエリを設定します。必要な計算処理に関するoptionを指定しています。optionでは、以下のHKStatisticsOptionsを指定できます。

変数名 説明
separateBySource 指定された統計情報を各ソース(端末情報・アプリ)ごとに個別に計算することを示すオプション。
discreteAverage サンプルの平均値を計算することを示すオプション。
discreteMin サンプルの最小値を計算することを示すオプション。
discreteMax サンプルの最大値を計算することを示すオプション。
cumulativeSum サンプルのすべての値の合計を計算することを示すオプション。
mostRecent 一致するサンプルから最新の値を返すことを示すオプション。

取得したい値の単位

let countPerMinuteUnit = HKUnit.count().unitDivided(by: .minute())

値に適した単位を紐づける必要があります。心拍数とは1分間のうちに心臓が拍動する回数であるため、回数を分で割る単位を定義します。

心拍数データの取得と処理

try await heartRateQuery.result(for: healthStore)
        .statistics().forEach { statistics in
        let average = statistics.averageQuantity()!.doubleValue(for: countPerMinuteUnit)
        let min = statistics.minimumQuantity()!.doubleValue(for: countPerMinuteUnit)
        let max = statistics.maximumQuantity()!.doubleValue(for: countPerMinuteUnit)
        let mostRecent = statistics.mostRecentQuantity()!.doubleValue(for: countPerMinuteUnit)
        // let sum = statistics.sumQuantity()!.doubleValue(for: countPerMinuteUnit)
        
        let stats = DailyHeartRateStats(
        date: dateFormatter.string(from: statistics.startDate),  // 日付を文字列に変換
        average: average,  // 平均心拍数
        min: min,  // 最低心拍数
        max: max,  // 最高心拍数
        mostRecent: mostRecent  // 最新の心拍数
        )
        dailyHeartRates.append(stats)
        }

作成したクエリを実行します。心拍数データを非同期で取得し、結果はHKStatisticsとして受け取ることが可能です。HKStatisticsで心拍数の値(平均、最小、最大、最新)を取り出して、最終的にDailyHeartRateStatsオブジェクト(筆者が定義した構造体)に格納しています。

ここで注意するポイントとしては、

  • クエリ作成の時、optionに必要な計算処理を設定していなければ値を取得できません
  • 今回は心拍数データを使用するため、心拍数を合計しても意味を持たない値が算出されるため、statistics.sumQuantity()を呼び出しても実行時エラーが出力されます。そのため今回はコメントアウトで実装しています

2点目は下記のようなエラーが出力されます。

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Statistics option HKStatisticsOptionCumulativeSum is not compatible with discrete data type HKQuantityTypeIdentifierHeartRate'

捕捉されなかった例外 'NSInvalidArgumentException'、理由: '統計オプション HKStatisticsOptionCumulativeSum は離散データ型 HKQuantityTypeIdentifierHeartRate と互換性がありません' のためアプリを終了します。(DeepL翻訳)

以上で、クエリを作成して実行するまでの実装できました。

最後に

間違い・気になる部分がありましたら、コメントいただけると大変うれしいです。
良かったと思ったら記事へのいいねXのフォローをよろしくお願いいたします。

https://sites.google.com/view/muranakar

個人でアプリを作成しています。良かったら覗いてみてください。

Discussion