← すべての記事

iOS 26のHealthKit + SwiftUI:認可、サンプルタイプ、そして2つの実アプリから得たクロスプラットフォームパターン

HealthKitは、SwiftUIアプリで正しく実装するのが最も難しいAppleフレームワークの1つです。認可フローには一時的な失敗による罠があり、ユーザーを永久にロックアウトしてしまう可能性があります。サンプルタイプは、定量データ(水分摂取、歩数、カロリーには HKQuantitySample)と分類データ(マインドフルセッション、睡眠、月経の流れには HKCategorySample)で扱いが分かれており、その分割は少し不格好です。async API の表面では、コールバックベースのHealthKit呼び出しを withCheckedThrowingContinuation でラップする必要があります。そしてwatchOSではパターンがまた変わります。

私はこれまで2つの本番アプリでHealthKitを実装してきました。Water(水分摂取トラッキング、約192行の HealthKitService1とReturn(マインドフルセッションのロギング、約171行の HealthKitManager と155行の HealthKitPermissionSheet2です。この2つのアプリは、HealthKitの主要なサンプル形状の両方、両方向(読み取り+書き込み vs. 書き込みのみ)、そして単一プラットフォームとクロスプラットフォーム両方のデプロイをカバーしています。

この記事では、本番環境で生き残ったパターンを紹介します。事前許可UX、SwiftUIの .healthDataAccessRequest モディファイア vs. レガシーの requestAuthorization API、サンプルクエリのasyncラッパー、そしてwatchOS固有の差分について解説します。

TL;DR

  • 認可ステータスのレポートは非対称です。AppleのAPIAPIは「共有が認可された」ことは確実に教えてくれますが、プライバシー上の理由から「読み取りが拒否された」ことは教えてくれません。本記事では、読み取り状態を推測することなく「ユーザーは尋ねられたが許可しなかった」を検出する方法を解説します。
  • 定量データは HKQuantityTypeHKUnit を使った HKQuantitySample を使用します(Waterは水分摂取に .literUnit(with: .milli) を使用)。分類データは HKCategoryType を使った HKCategorySample を使用します(Returnは .mindfulSession を使用)。
  • 事前許可シートは、最も見逃されがちなパターンです。Appleのシステム許可ダイアログは無機質ですが、最初に表示するカスタム View で価値を説明することで、許可率は劇的に向上します。
  • watchOS HealthKitには別の HKHealthStore インスタンスが必要で、認可の再プロンプトルールがより厳しく、事前許可UX用にSwiftUIのシートを表示できません。
  • Returnの recordAuthorizationAttempt() パターンは、一時的なプレゼンテーション失敗が永続的な拒否として扱われるのを防ぎます。

2つのサンプル形状

HealthKitは、サンプルモデルを軸に沿って分割します。この軸は、書くコードの一行一行に影響します3

サンプル形状 典型的な用途 API
HKQuantitySample 水分、歩数、カロリー、体重、心拍数 HKQuantityType + HKUnit + HKQuantity
HKCategorySample マインドフルセッション、睡眠、月経の流れ、性行為 HKCategoryType + HKCategoryValue
HKWorkout 構造化された運動(ランニング、水泳) HKWorkoutBuilderHKWorkoutSession

水分は数量です。HealthKitService.swift の本番コードを見てみましょう1

import HealthKit

@Observable
final class HealthKitService {
    static let shared = HealthKitService()

    private let healthStore = HKHealthStore()
    private let waterType = HKQuantityType(.dietaryWater)

    func logWater(amount: Double, date: Date = .now) async throws -> UUID {
        guard isAuthorized else { throw HealthKitError.notAuthorized }

        let quantity = HKQuantity(unit: .literUnit(with: .milli), doubleValue: amount)
        let sample = HKQuantitySample(
            type: waterType,
            quantity: quantity,
            start: date,
            end: date,
            metadata: [HKMetadataKeyWasUserEntered: true]
        )

        try await healthStore.save(sample)
        return sample.uuid
    }
}

本番運用から得た3つのポイント。

  1. .literUnit(with: .milli) は、ミリリットル単位の水分を扱う際の正規の単位です。HealthKitはあらゆる単位(米国液量オンス、リットル)を受け入れますが、Appleが内部的にすべてをリットルに正規化するため、コンストラクタの選択が重要となります。.milli を選択することで、小数のリットル値(0.240、0.500)ではなく、整数値(240、500)として保存できます。
  2. HKMetadataKeyWasUserEntered: true は、サンプルが計測されたものではなく、手動で入力されたものであることを示すフラグです。Healthアプリは、こうしたサンプルに小さな「手動入力」のインジケーターを表示します。ユーザーは、手動入力データと体重計などで計測されたデータを区別して信用するのです。
  3. 瞬間的なサンプルでは、startend は同じ Date となります。数量は [start, end] のウィンドウにわたって累積されますが、「いま240ml飲んだ」という場合には、ウィンドウは1点に収束します。

マインドフルセッションは分類型です。Returnの HealthKitManager.swift の本番コードを見てみましょう2

import HealthKit

@MainActor
class HealthKitManager {
    static let shared = HealthKitManager()
    let healthStore = HKHealthStore()
    let mindfulType = HKCategoryType(.mindfulSession)

    func saveMindfulSession(start: Date, end: Date) async -> Bool {
        guard isAvailable else { return false }
        guard end > start else { return false }

        let sample = HKCategorySample(
            type: mindfulType,
            value: HKCategoryValue.notApplicable.rawValue,
            start: start,
            end: end
        )
        // ... save via healthStore.save(sample) ...
    }
}

ポイントは2つ。

  1. HKCategoryValue.notApplicable.rawValue は、要となる番兵値です。マインドフルセッションには意味のある「値」が存在しない(持続時間のマーカーである)ため、HealthKitは型システムを満たすために、フィールドに番兵となるカテゴリ値(Int(0))を要求します。他のカテゴリサンプルはより豊富な値を持ちます。例えば、睡眠は HKCategoryValueSleepAnalysis.asleep などを使用します。
  2. カテゴリサンプルでは start/end のウィンドウは実体を持ちます。10分間のマインドフルセッションは、startend が10分離れており、HealthKitの日々のマインドフル分の集計はこれらの持続時間の合計として算出されます。

認可フロー(とその罠)

HealthKitの認可は、意図的に非対称になっています。authorizationStatus(for:) は共有/書き込みのステータスを正確に報告しますが(.sharingAuthorized.sharingDenied.notDetermined)、読み取りの許可や拒否はそのAPIAPIから直接観測できません。Appleは、アプリがユーザーのHealthプロフィールにどんなデータが存在するかを推測するのを防ぐため、意図的に読み取り状態を隠しています4。読み取りの判断は間接的に知ることになります。ユーザーが読み取りを許可した場合はクエリがデータを返し、拒否した場合はクエリが空の結果を返します。コード上で分岐するための「読み取りが拒否された」というシグナルは存在しません。

両方のアプリは、それぞれ異なる方法でこの問題に対処しています。

Waterは自身のサンプルを読み取ります。Waterは水分摂取エントリの書き込みと読み取りの両方を行うため(「今日の履歴」を表示するため)、認可フローでは「読み取り拒否が見えない」ケースに対処する必要があります1

private var typesToShare: Set<HKSampleType> { [waterType] }
private var typesToRead: Set<HKSampleType> { [waterType] }

func requestAuthorization() async throws {
    guard isHealthDataAvailable else { throw HealthKitError.notAvailable }

    try await healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead)

    await MainActor.run { checkAuthorizationStatus() }
}

func checkAuthorizationStatus() {
    authorizationStatus = healthStore.authorizationStatus(for: waterType)
    isAuthorized = authorizationStatus == .sharingAuthorized
}

ここで checkAuthorizationStatusしていないことに注目してください。読み取り認可ステータスを問い合わせていないのです。Waterは共有ステータスのみを確認しています。ユーザーが共有を許可しつつ読み取りを拒否した場合、読み取りが空を返すため(明示的なエラーが発生するわけではないため)、WaterのUIは「エントリなし」と表示します。Waterは共有の判断を信頼し、データの不在をそのまま表現させているのです。気になるユーザーは設定から修正できます。

Returnは書き込みのみ行います。Returnはマインドフルセッションをログに記録しますが、それらを読み返すことはありません。表示するセッションリストはHealthKitからではなく、自身の NSUbiquitousKeyValueStore から取得しています。そのため、Returnの認可リクエストは書き込み専用です2

func requestAuthorization() async -> Bool {
    guard isAvailable else { return false }

    do {
        try await healthStore.requestAuthorization(
            toShare: [mindfulType],
            read: []  // We only need write access
        )
        hasRequestedHealthKit = authorizationStatus != .notDetermined
        return isAuthorizedToWrite()
    } catch {
        hasRequestedHealthKit = authorizationStatus != .notDetermined
        return false
    }
}

read: [] の空セットは意図的なものです。不要な読み取りアクセスを要求すると、App Reviewで正当化しなければならない許可スコープが拡大し、アプリが明らかに必要としていないのに「Returnはあなたのマインドフルセッションを読み取りたい」というメッセージを目にしたユーザーを混乱させてしまいます。

罠について。両方のアプリが、同じ防御的なパターンに収束しました。認可試行を「完了」とマークするのは、ステータスが実際に .notDetermined を超えて変化した場合のみとする、というパターンです。素朴なコードはこうです。

hasRequestedHealthKit = true  // ❌ wrong: treats transient failures as denial

Returnの正しいパターンはこちらです2

hasRequestedHealthKit = authorizationStatus != .notDetermined  // ✓ checks real state

なぜ重要なのか。SwiftUIの .healthDataAccessRequest モディファイアと、その背後にある requestAuthorization APIAPIは、特定の条件下(シートの競合、ビューライフサイクルの中断、一時的なOS状態)でシステムダイアログの表示に失敗することがあります。実際のステータスを確認する前に試行を「完了」とマークすると、ユーザーは罠にはまります。アプリは「拒否された」と認識しているのに、システムダイアログは一度も表示されていないという状態です。ユーザーは、設定 → プライバシー → ヘルスから手動で許可するしか元に戻る道はありませんが、プロンプトを一度も見ていないため、そんなことを思いつきもしません。Returnの recordAuthorizationAttempt() は、まさにこのケースのために存在しているのです。

事前許可シート

AppleのHealthKit許可ダイアログは正しいものですが、訴求力に欠けます。アプリが共有または読み取りを希望するタイプのリストがトグル付きで表示されるだけです。アプリがなぜそのデータを必要としているのかというコンテキストもなければ、メリットの説明もなく、アプリのブランドも表示されません。プロンプトに文脈がないため、ユーザーは「許可しない」をタップしてしまうのです。

Returnでは、システムダイアログの前に表示される HealthKitPermissionSheet を実装しています。このシートには、ReturnアプリのアイコンとApple Healthアイコンが並んで表示され(HIG準拠:Apple Healthアイコンはクリッピングや影付けをしてはいけません)5、メリットを伝え(「Track Your Practice」/「Save your meditation sessions as Mindful Minutes in Apple Health」)、3つのメリット行(「See your practice in Apple Health」「Syncs across all your devices」「Works with other wellness apps」)をリストし、システムリクエストをトリガーする1つの前進ボタン Continue で締めくくります。HealthKitPermissionSheet.swift の実構造はこちらです6

struct HealthKitPermissionSheet: View {
    var onEnableRequested: () -> Void
    var theme: Theme

    var body: some View {
        VStack(spacing: 0) {
            HStack(spacing: 16) {
                Image("ReturnAppIcon")
                    .resizable().scaledToFit().frame(width: 80, height: 80)
                    .clipShape(RoundedRectangle(cornerRadius: 18))

                Text("+").font(.title)

                Image("AppleHealthIcon")
                    .resizable().scaledToFit().frame(width: 80, height: 80)
                    // No clipShape; Apple HIG forbids altering the Health icon
            }

            // Title + subtitle + benefits list

            // (See discussion below for the production comment.)
            Button { onEnableRequested() } label: {
                Text("Continue")
            }
        }
        .interactiveDismissDisabled(true)
    }
}

interactiveDismissDisabled(true) とキャンセルボタンを設けないことは、意図的なデザイン上の選択です。AppleのApp Reviewガイドライン5.1.1は、アプリがユーザーのプライバシー設定を尊重することを要求しており、同意を操作したり、騙したり、強制したりすることを禁じています10。本番コードの Continue ボタンの上にあるコメントはこう書かれています。

Single forward action: the system HealthKit dialog owns the real yes/no choice. Apple Guideline 5.1.1(iv): the priming screen may not include any exit/dismiss path that bypasses the system permission request.

このフレーミングは、ガイドラインの文字通りの記述(同意の尊重と強制の禁止について述べていますが、プライミング画面のUXについて言葉として規定しているわけではない)よりも厳格です。これはReturnの解釈であり、ソースコードに明文化されたものです。意図はこうです。拒否するなら、Returnの画面ではなく、Appleの画面で拒否すべきだ、と。結果として、シートには前進アクションが1つあり、逃げ道はありません。コピーとメリット行はタップを獲得しなければならず、「考えておく」という分岐は存在しません。

フローはこうなります。

  1. ユーザーがReturnの設定で「Connect Health」をタップする。
  2. 事前許可シートが表示される。ユーザーは説明を読む。
  3. ユーザーが Continue をタップする。シートはマウントされたまま、requestAuthorization が実行され、システムダイアログが表示される。
  4. ユーザーがシステムダイアログで承諾(または拒否)する。Returnは recordAuthorizationAttempt() を呼び出して結果を捕捉し、シートが閉じる。

なぜこれをやるのか。Appleは事前許可シートによる許可率向上の公式な数字を公表していません。しかし、A/Bテストとそれを実施する忍耐力の両方を持つiOS開発者と話したところ、全員が同じ方向性を報告していました。事前許可シートは共有認可率を劇的に高めます。このパターンは今や一般的なものとなり、Apple自身のテンプレート(写真、カメラ、位置情報)も独自の事前許可UXを取り入れることが増えてきています。

watchOSバリアント

watchOSのHealthKitは、iOSのHealthKitと同じAPIAPI面(HKHealthStore、サンプルタイプ、認可)を共有していますが、3つの構造的な差分があります。

  1. watchアプリごとに新しい HKHealthStore インスタンス。watchアプリとペアリングされたiPhoneアプリは、それぞれ独自の HKHealthStore インスタンスを持ちます。両方ともユーザーのHealthKitデータベースに書き込み可能で、両方とも自身のサンプルを読み取り可能です。ストアはペア間で共有されません
  2. 事前許可用のSwiftUIシートは使えない。watchOSのビュー階層は、iOSのようなシートをサポートしていません。事前許可UXはフルスクリーンでなければなりません。
  3. より厳格な再プロンプトルール。watchOSの requestAuthorization 呼び出しは、ユーザーが過去に拒否した場合のシステムダイアログの再表示について、より保守的に動作します。watch自体には設定 → プライバシー → ヘルスのUIがないため、設定変更にはiPhoneのWatchアプリへ誘導する必要が出てくるかもしれません。

WatchHealthKitManager.swift の本番コードを見てみましょう7

@MainActor
class WatchHealthKitManager {
    static let shared = WatchHealthKitManager()
    let healthStore = HKHealthStore()
    let mindfulType = HKCategoryType(.mindfulSession)

    private init() {}

    var isAvailable: Bool { HKHealthStore.isHealthDataAvailable() }

    /// Returns true if the system request completed (success or already-authorized).
    /// Caller checks isAuthorizedToWrite() separately for the actual share state.
    func requestAuthorization() async -> Bool {
        guard isAvailable else { return false }
        do {
            try await healthStore.requestAuthorization(toShare: [mindfulType], read: [])
            return true
        } catch {
            return false
        }
    }

    func isAuthorizedToWrite() -> Bool {
        guard isAvailable else { return false }
        return healthStore.authorizationStatus(for: mindfulType) == .sharingAuthorized
    }
    // ... saveMindfulSession identical to iOS variant ...
}

この分割は意図的なものです。requestAuthorizationシステムコールが成功したかどうかを報告し、isAuthorizedToWrite はユーザーが選択した結果を報告します。watchでこれらを分割することで、コードを推論しやすくしています。iOSでは、これに相当する分割は HealthKitManager 上の recordAuthorizationAttempt()isAuthorizedToWrite() のペアとして表れます。

WatchクラスはiOSクラスのほぼ半分のサイズですが、それは以下が不要だからです。

  • hasRequestedHealthKit UserDefaultsフラグ(watchのUXは2回目の試行パスをサポートする数が少ない)
  • HealthKitPermissionSheet の配管(watchOSにはシートUIがない)
  • recordAuthorizationAttempt() メソッド(watchの狭いフローには一時的失敗のエッジケースが少ない)

トレードオフとして、watchOSアプリは時折、システムダイアログを拒否し、iPhoneのWatchアプリで修正できることに気づいていないユーザーにとって、回復不能な拒否状態に陥ることがあります。Returnは、再プロンプトを試みる代わりに、こうした場合に小さなアプリ内の指示(「iPhoneでWatchアプリを開く → My Watch → プライバシー → ヘルス」)を表示しています。

もう一度作るならこうする

2つの本番アプリでHealthKitを実装した経験から得た3つの教訓です。

両方のAPIAPIが機能します。選択はプレゼンテーションコンテキストに依存します。SwiftUIの .healthDataAccessRequest モディファイアは、レガシーのAPIAPIをより宣言的な形でラップしており、iPadOSでプレゼンテーションコンテキストを正しく処理します。Returnはこのモディファイアを使用し、SwiftUIの親ビューがそれを接続できるよう、HealthKitManager 上にパブリックな mindfulShareTypes プロパティを介して共有タイプを公開しています。Waterはレガシーの healthStore.requestAuthorization を直接使用しています。Waterの認可フローは非Viewコンテキスト(@Observable サービス)から実行されるためです。この分割は有用なパターンとなります。リクエストがSwiftUIのライフサイクルイベント(シート内のボタンタップ)から発生する場合はモディファイアを優先し、サービスから発生する場合はレガシーAPIAPIにフォールバックします。

事前許可シートはiOSではエンジニアリングコストに見合いますが、watchOSでは見合いません。事前許可シートは、おそらく4時間程度の作業(シートビュー、コピー、テーマ設定、統合)で実装できます。iOSでの許可率向上効果は十分大きく、4時間は明らかに価値ある投資となります。watchOSでは、相当するフルスクリーンの事前許可はより邪魔になり(シートではなくwatch画面全体を占有する)、ユーザーは小さな画面で長いコピーを読む可能性が低く、watchのUXフローではHealthKitを必要とする機能をユーザーが要求するエントリポイントが少ないのです。私はwatchOSにそれを搭載せずにReturnをリリースしましたが、後悔したことはありません。

hasRequestedHealthKit を、ライブの認可ステータスとは別に追跡する。HealthKitのAPIAPIは現在の認可ステータスを教えてくれますが、過去に尋ねたことがあるかどうかは教えてくれません。この区別は重要です。なぜなら、正しい2回目のタップの動作はそれに依存するからです。1回目のタップでは requestAuthorization を呼び出し、2回目のタップでは、ユーザーが過去に拒否していた場合、APIAPIを再呼び出し(拒否状態では暗黙的にno-op)する代わりに「設定 → プライバシー → ヘルス」アラートを表示すべきなのです。hasRequestedHealthKit のUserDefaultsフラグが、2回目のタップを意味のあるものにします。

HealthKitを使うべきでない場面

拒否することもデザインの一部です。

できるからといってHealthKitに書き込むべきではない。フォーカスタイマーアプリはワークアウトを書き込む必要はありません。メモアプリはマインドフルネスをログに記録する必要はありません。「無料の統合」だからとHealthKitを追加すると、プライバシーフットプリント、認可の摩擦、App Review質問票のスコープが拡大しますが、ユーザーには実質的な価値を提供できません。

避けられるならHealthKitから読み取らない。読み取りアクセスは、App Reviewで正当化するのも、ユーザーに説明するのも難しくなります。HealthKitから読み取る多くのアプリは、書き込みのみで済ませて、ユーザーがApple Healthでデータを見られるようにすることが可能です。読み取りフローはUXの利得がほぼないにもかかわらず、表面積を倍増させます。

クロスデバイス専用のデータでMacのHealthKitを使わない。macOSはmacOS 13からHealthKitをサポートしていますが、HealthデータのほとんどはiPhoneやApple Watchが起点となっています。Macアプリで同じデータを必要とするなら、iPhoneでHealthKitに書き込み、Appleのクロスデバイス同期にMacへ表面化させてもらいましょう。MacからのHealthKit直接書き込みは有効ですが、実用例は稀です。

拒否後の取り消しパスをテストせずにリリースしない。ユーザーは許可した後、設定 → プライバシー → ヘルスから取り消すことがあります。アプリは取り消しを優雅に処理する必要があり、通常は次回その機能を使おうとした際に設定へのディープリンク指示を表示します。WaterもReturnもこのパスを実装していますが、初回でうまく動いたのはどちらでもありませんでした。

iOS 26+でHealthKitをリリースするSwiftUIアプリにとっての意味

3つのテイクアウェイです。

  1. UIを設計する前に、書き込み専用にするか読み取り+書き込みにするかを決める。書き込み専用は表面積が小さく、App Reviewが速くなります。読み取り+書き込みはより柔軟ですが、「読み取り拒否が見えない」非対称性が加わります。
  2. iOSでは事前許可シートをリリースする。許可率向上の効果は本物です。Appleのアイコンを正しく使い(クリッピングなし、影なし)、メリットを述べてから、システムAPIAPIを呼び出しましょう。
  3. watchOSのHealthKitはiOSパターンの小さく、シンプルなバリアントとして扱う。儀式は少なく、シートはなく、単一目的の認可。再許可にはiPhoneのWatchアプリへユーザーを誘導します。

この記事は、同じファミリーのアプリに関する以前の記事と組み合わせてお読みください。Apple Intelligence向けの型付き App Intents、クロスLLMエージェント向けの MCPサーバー、ビジュアルレイヤー向けの Liquid Glassパターン、クロスデバイスリーチ向けの マルチプラットフォームリリース。HealthKitはデータソースレイヤーであり、ビジュアルレイヤーや統合面の下に位置します8

FAQ

iOSアプリとそのwatchOS拡張で認可を共有できますか?

できません。iOSの HKHealthStore とwatchOSの HKHealthStore は独立しています。ユーザーは別々のシステムダイアログを通じて、各プラットフォームで個別に認可を付与します。それぞれのコードは自身の認可ステータスをチェックします。watchからiOSのステータスを読み取ることも、その逆もできません。

ユーザーが書き込みアクセスを取り消した場合、サンプルはどうなりますか?

既存のサンプルはHealthKit内に残ります。ユーザーが希望すればHealthアプリから手動で削除できますが、アプリのアクセスを取り消すと新規の書き込みのみが停止します。アプリはサンプルの保存や変更ができなくなりますが、ユーザーの履歴データは保持されます。

SwiftUIモディファイアの .healthDataAccessRequest は本番で安全に使えますか?

iOS 17.4+で動作し、その後のリリースで洗練されてきました。Returnはこのモディファイアを使用しています(SwiftUIに渡すために HealthKitManager 上に mindfulShareTypes を公開しています)。Waterはレガシーの healthStore.requestAuthorization を直接使用しています。Waterのリクエストは非Viewの @Observable サービスから発生するためです。リクエストがどこから開始されるかに基づいて選びましょう。SwiftUIライフサイクルイベントならモディファイア、サービスや非ViewコンテキストならレガシーAPIAPIです。

共有アクセスを一度も要求していないのに、HealthKitの認可ステータスが .sharingAuthorized を返すのはなぜですか?

そんなことはありません。ステータスは HKObjectType ごとに管理されています。authorizationStatus(for: heartRateType) をチェックして、心拍数を一度も要求していなければ、.notDetermined が返ります。ステータスは、特定のタイプに対する認可が成功した場合にのみ .sharingAuthorized に移行します。

HealthKitにプライバシーマニフェストは必要ですか?

はい。HealthKitに触れるアプリは、PrivacyInfo.xcprivacy(プライバシーマニフェスト)とApp Store Connectのプライバシー質問票で使用を宣言する必要があります。関連するエントリは Info.plistNSHealthShareUsageDescriptionNSHealthUpdateUsageDescription、そしてプライバシーマニフェストの対応する宣言です9

参考文献


  1. 本番コード Water/Water/Services/HealthKitService.swift(192行)。著者の Water、iOS、iPadOS、macOS、watchOS、visionOSで利用可能なSwiftUI水分摂取トラッキングアプリ。HKQuantityType(.dietaryWater) を使った HKQuantitySampleHKMetadataKeyWasUserEntered フラグを使用。 

  2. 本番コード Return/Return/HealthKitManager.swift(171行)。著者の Return、iOS、iPadOS、macOS、watchOS、tvOSで利用可能なSwiftUI瞑想タイマーアプリ。HKCategoryType(.mindfulSession) を使った HKCategorySampleHKCategoryValue.notApplicable.rawValue を使用。 

  3. Apple Developer、“HealthKit framework”。フレームワークの2つの主要なサンプルタイプは、HKQuantitySample(水分、歩数、カロリーなどの計測可能な数量用)と HKCategorySample(マインドフルセッション、睡眠、月経の流れなどの非定量的なイベント用)。HKWorkout は構造化された運動をカバー。 

  4. Apple Developer、“Authorizing access to health data”。Appleは、ユーザーのHealthプロフィールにどんなデータが存在するかをアプリが推測するのを防ぐため、意図的に読み取り拒否状態を隠している。authorizationStatus(for:) メソッドは共有アクセスについてのみ正直な結果を返す。 

  5. Apple Developer、“Apple Health icon usage” Human Interface Guidelines。AppleのHealthアイコンは変更、クリッピング、影付け、再着色をしてはならない。プロモーションや事前許可のUIで1:1の忠実度で再現すること。 

  6. 本番コード Return/Return/HealthKitPermissionSheet.swift(155行)。システムHealthKitダイアログをトリガーする前に表示される事前許可 View。ReturnアプリのアイコンとApple Healthアイコンを並べ、メリットを説明し、ユーザーのタップでコールバッククロージャを介して認可をトリガーする。 

  7. 本番コード Return/ReturnWatch Watch App/WatchHealthKitManager.swift(86行)。HealthKitManager のwatchOSバリアント。独立した HKHealthStorehasRequestedHealthKit フラグなし、許可シート配管なし。 

  8. 著者の分析:HealthKitはiOSアプリのデータソースレイヤー、App Intentsはシステムとなる AI面、MCPはクロスLLMエージェント面、Liquid Glassはビジュアル面である。これら4つのレイヤーは、Appleの全5プラットフォームにわたる単一のリリース製品へと統合される。 

  9. Apple Developer、“Privacy manifest files”。HealthKitの使用は PrivacyInfo.xcprivacy と、Info.plistNSHealthShareUsageDescription および NSHealthUpdateUsageDescription キーで宣言する必要がある。 

  10. Apple Developer、“App Review Guideline 5.1.1”。アプリはユーザーのプライバシー設定を尊重し、個人データの収集前に同意を求めなければならず、同意を操作したり、騙したり、強制したりすることはできない。本記事のプライミング画面の終了パスに関する正確な表現は、文字通りのガイドラインのテキストではなくReturnの解釈を反映している。 

関連記事

HealthKitワークアウトのライフサイクル:HKWorkoutSessionの状態とiOS 26のクロスプラットフォーム対応

watchOSのワークアウトセッションは6つの状態を持つライフサイクルで動作します。iOS 26ではHKWorkoutSessionがiPhoneにも対応しました。状態マシン、ライブビルダー、デリゲートを解説します。

2 分で読める

watchOSのランタイムは契約であり、バックグラウンドタスクではない

watchOSにはiOSのようなバックグラウンドはありません。WKExtendedRuntimeSessionが契約であり、これがなければ手首を下ろした瞬間にアプリは停止します。Returnで実装したパターンを紹介します。

2 分で読める

ループ・エンジニアリング――検証が安いところでループは勝つ

ループ・エンジニアリングを、Boris Cherny の全文書き起こしと照合する。彼が名前を挙げるループはどれも検証が安い。その制約こそが、何を自動化すべきかを決める。

4 分で読める