$npx -y skills add dpearson2699/swift-ios-skills --skill healthkitRead, write, and query Apple Health data using HealthKit. Covers HKHealthStore authorization, sample queries, statistics queries, statistics collection queries for charts, saving HKQuantitySample data, background delivery, workout sessions with HKWorkoutSession and HKLiveWorkoutB
| 1 | # HealthKit |
| 2 | |
| 3 | Read and write health and fitness data from the Apple Health store. Covers authorization, queries, writing samples, background delivery, and workout sessions. Targets Swift 6.3 / iOS 26+. |
| 4 | |
| 5 | ## Contents |
| 6 | |
| 7 | - [Setup and Availability](#setup-and-availability) |
| 8 | - [Authorization](#authorization) |
| 9 | - [Reading Data: Sample Queries](#reading-data-sample-queries) |
| 10 | - [Reading Data: Statistics Queries](#reading-data-statistics-queries) |
| 11 | - [Reading Data: Statistics Collection Queries](#reading-data-statistics-collection-queries) |
| 12 | - [Writing Data](#writing-data) |
| 13 | - [Background Delivery](#background-delivery) |
| 14 | - [Workout Sessions](#workout-sessions) |
| 15 | - [Common Data Types](#common-data-types) |
| 16 | - [HKUnit Reference](#hkunit-reference) |
| 17 | - [Common Mistakes](#common-mistakes) |
| 18 | - [Review Checklist](#review-checklist) |
| 19 | - [References](#references) |
| 20 | |
| 21 | ## Setup and Availability |
| 22 | |
| 23 | ### Project Configuration |
| 24 | |
| 25 | 1. Enable the HealthKit capability in Xcode (adds the entitlement) |
| 26 | 2. Add `NSHealthShareUsageDescription` (read) and `NSHealthUpdateUsageDescription` (write) to Info.plist |
| 27 | 3. For background delivery, enable the "Background Delivery" sub-capability |
| 28 | |
| 29 | ### Availability Check |
| 30 | |
| 31 | Always check availability before calling other HealthKit APIs. Health data is |
| 32 | available on iOS, watchOS, visionOS, iPadOS 17+, and iOS apps running on |
| 33 | Vision Pro. It is unavailable on iPadOS 16 or earlier and may be restricted by |
| 34 | managed device policy. |
| 35 | |
| 36 | ```swift |
| 37 | import HealthKit |
| 38 | |
| 39 | guard HKHealthStore.isHealthDataAvailable() else { |
| 40 | // Health data is unavailable or restricted on this device. |
| 41 | return |
| 42 | } |
| 43 | |
| 44 | let healthStore = HKHealthStore() |
| 45 | ``` |
| 46 | |
| 47 | Create a single `HKHealthStore` instance and reuse it throughout your app. It |
| 48 | is thread-safe. If HealthKit is optional, review Xcode's generated |
| 49 | `UIRequiredDeviceCapabilities` `healthkit` entry so unsupported devices are not |
| 50 | excluded unintentionally. |
| 51 | |
| 52 | ## Authorization |
| 53 | |
| 54 | Request only the types your app genuinely needs. App Review rejects apps that over-request. |
| 55 | |
| 56 | ```swift |
| 57 | func requestAuthorization() async throws { |
| 58 | let typesToShare: Set<HKSampleType> = [ |
| 59 | HKQuantityType(.stepCount), |
| 60 | HKQuantityType(.activeEnergyBurned) |
| 61 | ] |
| 62 | |
| 63 | let typesToRead: Set<HKObjectType> = [ |
| 64 | HKQuantityType(.stepCount), |
| 65 | HKQuantityType(.heartRate), |
| 66 | HKQuantityType(.activeEnergyBurned), |
| 67 | HKCharacteristicType(.dateOfBirth) |
| 68 | ] |
| 69 | |
| 70 | try await healthStore.requestAuthorization( |
| 71 | toShare: typesToShare, |
| 72 | read: typesToRead |
| 73 | ) |
| 74 | } |
| 75 | ``` |
| 76 | |
| 77 | ### Checking Authorization Status |
| 78 | |
| 79 | `authorizationStatus(for:)` reports write/share authorization. HealthKit does |
| 80 | not reveal whether read permission was granted or denied. If the user denies |
| 81 | read access, queries return only samples your app successfully saved, which may |
| 82 | look like empty or partial data. |
| 83 | |
| 84 | ```swift |
| 85 | let status = healthStore.authorizationStatus( |
| 86 | for: HKQuantityType(.stepCount) |
| 87 | ) |
| 88 | |
| 89 | switch status { |
| 90 | case .notDetermined: |
| 91 | // Haven't requested yet -- safe to call requestAuthorization |
| 92 | break |
| 93 | case .sharingAuthorized: |
| 94 | // User granted write access |
| 95 | break |
| 96 | case .sharingDenied: |
| 97 | // User denied write access (read denial is indistinguishable from "no data") |
| 98 | break |
| 99 | @unknown default: |
| 100 | break |
| 101 | } |
| 102 | ``` |
| 103 | |
| 104 | ## Reading Data: Sample Queries |
| 105 | |
| 106 | Use `HKSampleQueryDescriptor` (async/await) for one-shot reads. Prefer descriptors over the older callback-based `HKSampleQuery`. |
| 107 | |
| 108 | ```swift |
| 109 | func fetchRecentHeartRates() async throws -> [HKQuantitySample] { |
| 110 | let heartRateType = HKQuantityType(.heartRate) |
| 111 | |
| 112 | let descriptor = HKSampleQueryDescriptor( |
| 113 | predicates: [.quantitySample(type: heartRateType)], |
| 114 | sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)], |
| 115 | limit: 20 |
| 116 | ) |
| 117 | |
| 118 | let results = try await descriptor.result(for: healthStore) |
| 119 | return results |
| 120 | } |
| 121 | |
| 122 | // Extracting values from samples: |
| 123 | for sample in results { |
| 124 | let bpm = sample.quantity.doubleValue( |
| 125 | for: HKUnit.count().unitDivided(by: .minute()) |
| 126 | ) |
| 127 | print("\(bpm) bpm at \(sample.endDate)") |
| 128 | } |
| 129 | ``` |
| 130 | |
| 131 | ## Reading Data: Statistics Queries |
| 132 | |
| 133 | Use `HKStatisticsQueryDescriptor` for aggregated single-value stats (sum, average, min, max). |
| 134 | |
| 135 | ```swift |
| 136 | func fetchTodayStepCount() async throws -> Double? { |
| 137 | let calendar = Calendar.current |
| 138 | let startOfDay = calendar.startOfDay(for: Date()) |
| 139 | let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)! |
| 140 | |
| 141 | let predicate = HKQuery.predicateForSamples( |
| 142 | withStart: startOfDay, end: endOfDay |
| 143 | ) |
| 144 | let stepType = HKQuantit |