iOS 27のHealthKit: ワークアウトゾーンと新しいタイプ
HealthKitは長年、ワークアウトの心拍ゾーンを非公式な存在として扱ってきました。5つのゾーンの棒グラフを描くランニングアプリやサイクリングアプリは、どれもゾーンを自前で計算していたのです。生のHKQuantitySample心拍データを取得し、ハードコードされた、あるいはユーザーが入力したしきい値でバケットに振り分け、各バケットに費やした時間を手作業で合計していました。iOS 27は、この手作業に終止符を打ちます。ゾーンは構造化されたHealthKitのタイプとなり、システムが生成し、本人がヘルスケアの設定で編集でき、アプリは時間集計がすでに済んだ状態で読み戻せるようになります。同じリリースでは、生殖関連の健康を扱う2つのカテゴリタイプ、menopausalStateとbleedingAfterMenopauseが追加され、HealthKitの周期トラッキングモデルの空白を埋めます。1
iOS 27の追加機能には2つの流れがあり、両者は同じ形を共有しています。ワークアウトゾーンの流れは、かつてアプリが抱え込んでいた計算をフレームワーク側へ移します。そこでは、一人ひとりのゾーン定義が、それを問い合わせるすべてのアプリで一貫して保たれます。新タイプの流れは、これまでHealthKitが表現できなかったライフステージに、型付きのカテゴリサンプルを与えます。どちらも既存のHealthKitの文法に従っています。測定値には quantity タイプ、イベントや状態には category タイプ、というわけです。本記事では、それぞれをAppleのドキュメントに照らして見ていきます。このクラスターでおなじみの視点、つまり「すでにHealthKitを組み込んでいるアプリが、それぞれの機能を得るために何を足すのか」という枠組みで進めます。
TL;DR
HKWorkoutZoneConfigurationは、ある quantity タイプに対するゾーンの完全な集合を定義します。システムは本人の健康指標からゾーンを自動生成し、本人はヘルスケアの設定でそれを編集でき、アプリは特定のワークアウト向けにカスタムゾーンを供給できます。2HKWorkoutZoneGroupは、ゾーン構成とその time-in-zone データを組にします。完了したワークアウトのインスタンスから読み戻すことで、心拍サンプルを自分でバケットに振り分けることなく、フレームワークがすでに計算したゾーンごとの時間が得られます。3HKHealthStoreのpreferredWorkoutZoneConfiguration(for:)は、ある quantity タイプに対する本人の優先ゾーン構成を返します。これにより、グラフが本人がどこでも目にしているしきい値とそろうのです。4menopausalStateとHKCategoryValueMenopausalState列挙型は、ある時点における閉経状態を記録します。サンプルの開始日と終了日は同一でなければならず、そうでなければ保存に失敗します。56bleedingAfterMenopauseは、閉経後の出血を強度の値を持つ区間として記録し、月経の経血とは臨床的に明確に区別されます。7
ワークアウトゾーンは、いつだって自前で計算するものだった
iOS 27より前、心拍ゾーンを扱いたいワークアウトアプリは、その仕事をすべて自分でこなしていました。ワークアウトの時間範囲について心拍のHKQuantitySampleデータを問い合わせ、ゾーンの境界をどこに置くかを決め(220から年齢を引いた値、乳酸性作業閾値に対する割合、あるいはユーザーが設定画面に入力した値)、サンプルを順にたどり、各帯域に費やした秒数を積み上げていきました。境界の計算はアプリごとにまちまちだったので、2つのアプリで走った人は、2通りの「ゾーン3」を目にしました。ゾーンのデータはどれもHealthKitには存在しなかったため、同期もされず、持ち運びもできませんでした。
iOS 27はHKWorkoutZoneConfigurationを導入します。これは、ある quantity タイプに対するゾーンの完全な集合を定義する構造体です。2 この構成は、順序付きのゾーンの配列を持ち、それが適用される quantity タイプを示します。重要な変化は、ゾーンがどこで生まれるかという点にあります。Appleのドキュメントによれば、システムは本人の健康指標に基づいてゾーンを自動生成し、本人はヘルスケアの設定で手動でゾーンを構成でき、あるいはアプリが特定のワークアウト向けにカスタムゾーンを供給できます。2 ゾーンの定義は、もはや各アプリの内部的な事情ではなくなり、フレームワークが所有する共有された状態となるのです。
import HealthKit
let heartRateType = HKQuantityType(.heartRate)
// A custom configuration an app supplies for one workout.
// makeIntervalZones is your own builder; construct the configuration
// from the ordered zones your workout uses, per HKWorkoutZoneConfiguration's
// declaration. The configuration identifies the quantity type the zones apply to.
let configuration: HKWorkoutZoneConfiguration = makeIntervalZones(for: heartRateType)
対になる構造体がHKWorkoutZoneGroupで、これは ある quantity タイプに対するゾーン構成と time-in-zone データを保持します。3 ゾーングループはHKWorkoutZoneConfigurationとゾーンごとの時間データを結びつけるので、グループを読むだけで境界と、本人がそれぞれの中で過ごした時間の両方が手に入ります。Appleのドキュメントは、完了したワークアウトのインスタンスからゾーングループにアクセスして終わったワークアウトのゾーンデータを取り出す方法と、ライブワークアウトのソースからアクティブなセッション中のリアルタイムなゾーン情報を取り出す方法を説明しています。3 バケットへの振り分けはフレームワークが行います。あなたのコードは、その結果を読むだけです。
セッション207でAppleは、ワークアウトゾーンがHealthKitに統合されたことで、各ゾーンでの時間はワークアウト中の受信サンプルから自動的に計算されると述べています。そのため、アプリは心拍サンプルを自分でバケットに振り分けるのではなく、zoneGroupsByTypeから集計済みの時間を読むだけで済みます。8
import HealthKit
func summarize(_ group: HKWorkoutZoneGroup) {
// The group bundles the configuration (the zone boundaries)
// with the time-in-zone data the framework already computed.
let configuration = group.configuration
for zone in group.zones {
// Render each zone's accumulated time. No sample-walking,
// no manual bucketing — the durations arrive pre-summed.
render(zone)
}
}
3つ目の部品は、構成をアプリではなく本人に結びつけます。preferredWorkoutZoneConfiguration(for:)はHKHealthStoreのインスタンスメソッドで、指定した quantity タイプに対する本人の優先ゾーン構成を返します。4
func preferredWorkoutZoneConfiguration(
for quantityType: HKQuantityType
) async throws -> HKWorkoutZoneConfiguration?
Appleのドキュメントは戻り値の契約を明確に定めています。このメソッドは、ヘルスケアの設定で本人が手動構成したゾーンを返し、本人がカスタム値を設定していなければシステム生成のゾーンを返し、その quantity タイプについてゾーンをまったく構成していなければnilを返します。4 システム生成のゾーンは、本人の健康指標が変化するにつれて定期的に更新されます。本人が手動で設定したゾーンは、本人が再び編集するまで変わりません。ドキュメントが意図しているのは、アプリがどのワークアウトでも本人の好みにそろったゾーン情報を表示することです。4 あなたの「ゾーン3」を、本人がどこでも目にしている「ゾーン3」に一致させたいなら、自前のゾーンをこしらえるのではなく、このメソッドに手を伸ばしましょう。
import HealthKit
let store = HKHealthStore()
let heartRateType = HKQuantityType(.heartRate)
func loadPreferredZones() async throws -> HKWorkoutZoneConfiguration? {
// nil means the person has not configured zones for heart rate.
// Fall back to your own defaults only in that case.
try await store.preferredWorkoutZoneConfiguration(for: heartRateType)
}
この非対称性には名前を与えておく価値があります。preferredWorkoutZoneConfiguration(for:)は本人の常設の好みを読み、ワークアウトの前・最中・後を通じて一貫したグラフを描きます。自分で組み立てるカスタムのHKWorkoutZoneConfigurationは、特定のワークアウトが本人のデフォルトとは異なるゾーンを必要とする場合、たとえば独自の帯域を持つ構造化されたインターバルセッションのような場面を受け持ちます。たいていのアプリは優先構成を使いたいはずです。カスタムの道は、ワークアウトの構造そのものが独自のゾーンを要求するときのためのものです。
新しいカテゴリタイプ: menopausalStateとbleedingAfterMenopause
HealthKitの周期トラッキングモデルは月経を扱えましたが、本人が閉経の移行のどこに位置するかを記録する型付きの手段はなく、月経が終わったあとに起こる出血を記録する手段もありませんでした。iOS 27はその両方をカテゴリタイプとして追加します。iOS 26のHealthKit記事がマインドフルネスのセッションや睡眠について取り上げたのと同じHKCategorySampleのパターンに従います。
menopausalStateは、本人の閉経状態を記録するサンプルのためのカテゴリタイプ識別子です。5 各サンプルは時点のエントリであり、Appleのドキュメントは厳しい制約を課しています。開始日は終了日と等しくなければならず、両者が異なるサンプルを保存しようとするとエラーになります。5 各サンプルの値はHKCategoryValueMenopausalState列挙型のケースで、記録された時点における閉経状態を示します。6 このファクトシートのケース一覧はAppleの解説では省略されているため、ケース名を当て推量するのではなく、有効な値の典拠としてこの列挙型そのものを扱ってください。ドキュメントが述べる振る舞いは、各サンプルがそのようなケースを1つ、特定の日付に保存する、というものです。6
import HealthKit
let store = HKHealthStore()
let menopausalType = HKCategoryType(.menopausalState)
func record(state: HKCategoryValueMenopausalState, on date: Date) async throws {
// Point-in-time sample: start and end MUST be identical.
// A mismatched start/end is a save error, by documented design.
let sample = HKCategorySample(
type: menopausalType,
value: state.rawValue,
start: date,
end: date
)
try await store.save(sample)
}
この時点モデルは、データの解釈の仕方を形づくります。Appleのドキュメントは、アプリが時系列で複数のサンプルを読み、より高いレベルの解釈、つまりアクティブな期間や移行、現在の状態を導き出せると指摘しています。6 単一のサンプルは、ある特定の状態が特定の日付に当てはまったと主張するにすぎず、サンプルをタイムラインへ縫い合わせる仕事はフレームワークがアプリに委ねます。Appleは各エントリを、状態の変化、その日付に状態が当てはまったという確認、あるいはその両方として位置づけており、どう読むかはアプリが系列をどう扱うかによります。5
bleedingAfterMenopauseは、閉経後の出血を記録するサンプルのためのカテゴリタイプ識別子です。7 独立したタイプとして存在する理由は、臨床上の区別にあります。閉経後は月経が終わっているため、閉経後の出血は月経の経血でもなければ、月経間出血でもありません。Appleのドキュメントは、それを両者と臨床的に明確に区別されるものと呼んでいます。7 時点のサンプルである閉経状態とは異なり、bleedingAfterMenopauseのサンプルは出血の区間を表し、強度の値を保持します。7
import HealthKit
let store = HKHealthStore()
let bleedingType = HKCategoryType(.bleedingAfterMenopause)
// `intensityValue` is the raw value of the intensity enum Apple defines for
// the bleedingAfterMenopause type; confirm the concrete case set in its declaration.
func record(intensityValue: Int, from start: Date, to end: Date) async throws {
// An interval sample, not point-in-time: start and end differ,
// and the value carries the bleeding intensity.
let sample = HKCategorySample(
type: bleedingType,
value: intensityValue,
start: start,
end: end
)
try await store.save(sample)
}
2つのタイプの分かれ目は、iOS 26の記事が時点のカテゴリサンプルと区間のカテゴリサンプルの間に引いた分かれ目を映しています。menopausalStateはその窓を単一の瞬間へと畳み込み、状態を運びます。bleedingAfterMenopauseは本物の[start, end]の窓を保ち、強度を運びます。どちらかで誤った形を選べば、保存エラーになるか、意味をなさない記録になります。つまり、選んだタイプは、クエリのコードを一行も書く前から、データの性質を符号化しているのです。
導入の道筋
すでにHealthKitを組み込んでいるワークアウトアプリやヘルスアプリは、iOS 27の領域を段階的に足していけます。そのどれも、iOS 26のパターン記事が取り上げた認可やサンプル保存の配管を書き直すものではありません。
- ゾーン計算を消す。 心拍ゾーンを自前で計算しているアプリは、該当する quantity タイプについて
preferredWorkoutZoneConfiguration(for:)を呼び、返された構成を描画するべきです。呼び出しがnilを返したときにのみ、自前のデフォルトに戻します。4 これでゾーンは、本人がシステム全体で目にしているものと一致します。 - サンプルをバケットに振り分けず、
HKWorkoutZoneGroupを読む。 完了したワークアウトのサマリーでは、生の心拍サンプルをたどって時間を自分で積み上げるのではなく、ゾーングループを取り出し、その計算済みの time-in-zone データを描画します。3 - ワークアウトが独自の帯域を必要とするときだけ、カスタムの
HKWorkoutZoneConfigurationを供給する。 あつらえのゾーンを持つ構造化されたインターバルセッションが、カスタム構成にふさわしい場面です。それ以外はすべて本人の優先構成を使います。2 - 新しいカテゴリタイプを認可リクエストに加える。 周期トラッキングアプリは、
menopausalStateとbleedingAfterMenopauseを共有・読み取りの集合に加え、menopausalStateを時点のサンプル(開始と終了が等しい)として記録し、bleedingAfterMenopauseを強度を持つ区間のサンプルとして記録します。57 - タイプごとに開始日と終了日を点検する。 閉経状態の保存は開始と終了が異なると失敗します。出血のサンプルは区間であり、両者が異なることを前提とします。保存の経路を出荷する前に、窓の形を正しく整えましょう。57
FAQ
iOS 27でも、心拍ゾーンは自分で計算しなければなりませんか?
いいえ、一般的なケースでは不要です。preferredWorkoutZoneConfiguration(for:)は、ある quantity タイプに対する本人の優先ゾーン構成(ヘルスケアの設定で手動構成したゾーン、システム生成のゾーン、あるいは一つも構成していなければnil)を返し、HKWorkoutZoneGroupはフレームワークがそのワークアウトについてすでに計算した time-in-zone データを保持します。34 自分でゾーンを計算するのは、特定のワークアウトが本人のデフォルトと異なるカスタムの帯域を必要とする場合だけで、そのときはそのワークアウト向けにHKWorkoutZoneConfigurationを組み立てます。2
HKWorkoutZoneConfigurationとHKWorkoutZoneGroupの違いは何ですか?
HKWorkoutZoneConfigurationは、ある quantity タイプに対するゾーンの完全な集合を定義します。順序付きのゾーンと、それらが適用される quantity タイプです。2 HKWorkoutZoneGroupは、その構成と、ある quantity タイプに対する time-in-zone データの両方を保持します。そのため、グループは境界と、本人が各ゾーンで過ごした時間を教えてくれます。3 ゾーングループは、終わったセッションについては完了したワークアウトのインスタンスから読み、リアルタイムなゾーン情報についてはライブのソースから読みます。3
本人が一度もゾーンを設定していないとき、preferredWorkoutZoneConfiguration(for:)は何を返しますか?
本人がカスタム値を設定していなければシステム生成のゾーンを返し、その quantity タイプについてゾーンをまったく構成していない場合にのみnilを返します。4 本人がヘルスケアの設定で手動でゾーンを設定していれば、メソッドはそれを返し、本人が編集するまでそれは変わりません。システム生成のゾーンは、本人の健康指標が変化するにつれて定期的に更新されます。4 nilで分岐して、自前のフォールバックのデフォルトを供給しましょう。
なぜmenopausalStateのサンプルを保存すると日付エラーで失敗するのですか?
menopausalStateは単一の時点における状態を記録するため、フレームワークはサンプルの開始日と終了日が同一であることを要求し、日付が異なるサンプルは保存時にエラーになります。5 両方を同じDateに設定してください。この制約が、区間を表し開始と終了が異なることを前提とするbleedingAfterMenopauseとの違いを際立たせています。7
bleedingAfterMenopauseは月経の経血とどう違いますか?
閉経後は月経が終わっているため、閉経後の出血は月経の経血とも月経間出血とも臨床的に明確に区別されます。Appleがそれに別個のカテゴリタイプを与えているのは、そのためです。7 各bleedingAfterMenopauseのサンプルは区間と強度の値を保持し、関連する状態のトラッキングにはmenopausalStateタイプを使います。7
Apple Ecosystemクラスターの全体が、この記事と並んで揃っています。これが土台とするiOS 26のHealthKit認可とサンプルタイプのパターン、ライブセッション中にゾーンデータが生まれるwatchOSのワークアウトのライフサイクル、手首でのバックグラウンド実行を司るwatchOSのランタイム契約、そしてHealthKitをデータソース層として位置づけるiOSアプリの3つの面です。ハブはApple Ecosystemシリーズです。AIエージェントを伴うiOSという、より広い文脈については、iOSエージェント開発ガイドをご覧ください。
参考文献
-
Apple Developer Documentation: HealthKit. quantity タイプ、category タイプ、サンプル、そしてiOS 27のワークアウトゾーンと生殖関連の健康の追加機能を網羅するフレームワークのリファレンス。 ↩
-
Apple Developer Documentation:
HKWorkoutZoneConfiguration(iOS 27.0). 「A structure that defines a complete set of zones for a quantity type.」struct HKWorkoutZoneConfigurationとして宣言され、順序付きのゾーンの配列を持ち、その quantity タイプを示し、システム生成・本人構成・アプリ供給のカスタムゾーンに対応する。 ↩↩↩↩↩↩ -
Apple Developer Documentation:
HKWorkoutZoneGroup(iOS 27.0). 「A structure that contains zone configuration and time-in-zone data for a quantity type.」struct HKWorkoutZoneGroupとして宣言され、構成と time-in-zone データを結びつけ、完了したワークアウトのインスタンスとライブワークアウトのソースからアクセスできる。 ↩↩↩↩↩↩↩ -
Apple Developer Documentation:
preferredWorkoutZoneConfiguration(for:)(iOS 27.0). 「Returns someone’s preferred zone configuration for the specified quantity type.」func preferredWorkoutZoneConfiguration(for quantityType: HKQuantityType) async throws -> HKWorkoutZoneConfiguration?として宣言され、手動構成したゾーン、システム生成のゾーン、またはnilを返す。 ↩↩↩↩↩↩↩↩ -
Apple Developer Documentation:
menopausalState(iOS 27.0). 「An identifier for samples that record a person’s menopausal state.」static let menopausalState: HKCategoryTypeIdentifierとして宣言され、時点のサンプルは開始日と終了日が同一であることを要求し、そうでなければ保存に失敗する。 ↩↩↩↩↩↩↩ -
Apple Developer Documentation:
HKCategoryValueMenopausalState(iOS 27.0). 「A value that indicates the menopausal state at a recorded point in time.」enum HKCategoryValueMenopausalStateとして宣言され、アプリは時系列で複数のサンプルを問い合わせて、アクティブな期間・移行・現在の状態を導き出せる。 ↩↩↩↩ -
Apple Developer Documentation:
bleedingAfterMenopause(iOS 27.0). 「An identifier for samples that record bleeding after menopause.」static let bleedingAfterMenopause: HKCategoryTypeIdentifierとして宣言され、月経の経血とは臨床的に明確に区別され、各サンプルは強度の値を持つ区間である。 ↩↩↩↩↩↩↩↩↩ -
Apple, WWDC26 session 207, “Deliver workout insights with HealthKit workout zones.” developer.apple.com/videos/play/wwdc2026/207. iOS 27で心拍とサイクリングパワーのゾーンがHealthKitに統合されたことにより、各ゾーンでの時間はワークアウト中の受信サンプルに基づいて自動的に計算され、アプリは
HKWorkoutまたはHKWorkoutActivityのzoneGroupsByType辞書からそれを読み戻す。 ↩