Integrating HealthKit: Health and Workout Data in iOS
We develop iOS apps with HealthKit integration, and here's the real problem almost every client faces: after the first version, the app gets rejected in the App Store due to incorrect permission requests or violation of Guideline 5.1.1. About 30% of apps using HealthKit pass review only on the second attempt. HealthKit isn't just an API for reading data from the Apple Watch—it's iOS's central health repository with a rigid schema, granular permissions per type, and strict policies from HealthKit.
Over the years, we've completed more than 50 HealthKit integrations for clients in fitness, medicine, and insurance. Our engineers have developed a checklist that cuts the App Store approval timeline by an average of two weeks.
How App Store Review Affects HealthKit Integration
Apple manually reviews every HealthKit integration during each review. The main reasons for rejection:
- The app requests data types it doesn't use (
HKObjectTypemust match actual functionality). - Missing
NSHealthShareUsageDescription/NSHealthUpdateUsageDescriptioninInfo.plist—a trivial crash on first request. - The app requests write permission for workouts but isn't a fitness app—rejection under Privacy (Section 5.1.1).
A quirk of HealthKit permissions: the user can deny access to a specific type, but the app never learns about it explicitly. HKHealthStore.authorizationStatus(for:) returns .notDetermined both when denied and when not yet asked. This is a privacy safeguard—you cannot infer the existence of data from the authorization status.
The practical consequence: you should never show an alert like "You denied access to steps." Instead, silently try to read the data, and if the array is empty, show a neutral message "data unavailable" with a button "Open Health."
Why Query Type Selection Is Critical
HKSampleQuery is suitable for raw samples: each heart rate measurement, each step. For an active user over a year, tens of thousands of records accumulate—a query without a limit and sorting will cause an OutOfMemory crash. Always use limit and sortDescriptors:
let query = HKSampleQuery( sampleType: HKQuantityType(.heartRate), predicate: HKQuery.predicateForSamples( withStart: startDate, end: endDate, options: .strictStartDate ), limit: 1000, sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)] ) { _, samples, error in guard let samples = samples as? [HKQuantitySample] else { return } let bpmValues = samples.map { $0.quantity.doubleValue(for: .init(from: "count/min")) } // processing } healthStore.execute(query) HKStatisticsQuery processes aggregated data 10 times faster than HKSampleQuery for tasks like total steps per day. For interval statistics (day, week) over a period, use HKStatisticsCollectionQuery:
let interval = DateComponents(day: 1) let query = HKStatisticsCollectionQuery( quantityType: HKQuantityType(.stepCount), quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: Calendar.current.startOfDay(for: Date()), intervalComponents: interval ) query.initialResultsHandler = { _, results, _ in results?.enumerateStatistics(from: startDate, to: endDate) { stat, _ in let steps = stat.sumQuantity()?.doubleValue(for: .count()) ?? 0 } } HKAnchoredObjectQuery is for background updates: the app receives only the delta since the last query.
| Query Type | Purpose | Performance |
|---|---|---|
| HKSampleQuery | Raw samples | Medium (memory-constrained) |
| HKStatisticsQuery | Aggregates (sum, average) | High (10× faster) |
| HKAnchoredObjectQuery | Delta updates | High (only new data) |
How to Record a Workout: HKWorkoutBuilder in Real Time
For recording an active workout—always use HKWorkoutBuilder, not the old HKWorkout(activityType:start:end:). The builder allows adding samples in real time:
let config = HKWorkoutConfiguration() config.activityType = .running config.locationType = .outdoor let builder = HKWorkoutBuilder(healthStore: healthStore, configuration: config, device: .local()) builder.beginCollection(withStart: Date()) { success, error in // workout started } // every 5 seconds add heart rate let heartRateSample = HKQuantitySample( type: HKQuantityType(.heartRate), quantity: HKQuantity(unit: .init(from: "count/min"), doubleValue: 142), start: Date(), end: Date() ) builder.add([heartRateSample]) { _, _ in } // finish builder.endCollection(withEnd: Date()) { _, _ in builder.finishWorkout { workout, error in // workout saved to HealthKit } } Common Mistakes with HealthKit Integration
- Calling HealthKit API on the main actor without
async/await—blocks the UI on slow queries to large datasets. - Not checking
HKHealthStore.isHealthDataAvailable()—HealthKit is unavailable on iPads without an Apple Watch. - Reading heart rate in
count/minunits instead ofHKUnit(from: "count/min")—results will be incorrect.
Full list of HealthKit data types we work with
- Steps (stepCount) and distance (distanceWalkingRunning)
- Heart rate (heartRate) and variability (heartRateVariabilitySDNN)
- Resting and active energy (basalEnergyBurned, activeEnergyBurned)
- Sleep (sleepAnalysis)—categories: inBed, asleep, awake
- Weight, height, body mass index
- Blood glucose, blood pressure, blood oxygen
- Workouts with metadata: type, duration, calories
What's Included in the Work: Deliverables
- Integration code for reading and writing required data types.
- Permissions request screen with informational text.
- Handling of all edge cases (no data, denial, empty results).
- Background synchronization with the server via
HKAnchoredObjectQuery. - Documentation on working with HealthKit for your team.
- Consulting on passing App Store review.
Estimated Timelines
| Scenario | Timeline |
|---|---|
| Reading steps, heart rate, and workouts | 5–8 business days |
| Workout recording + background sync | 2–3 weeks |
| Full cycle (read, write, permissions screen, deployment) | from 3 weeks |
Cost is determined individually after analyzing your project. Order a consultation—we'll evaluate the scope and prepare a commercial proposal. Contact us to discuss the details of HealthKit integration into your app. We guarantee App Store review approval.







