Home / Blog / HealthKit integration patterns: practical notes from 3 apps

HealthKit integration patterns: practical notes from 3 apps

I've used HealthKit in ByteBreak, Dentii, and Snoozio. Notes on authorization, background delivery, and interpreting the data.

HealthKit is one of Apple’s most powerful frameworks. It’s also one of the trickiest. Privacy rules, background-delivery surprises, tangled data units. I’ve used HealthKit in three apps, each one taught me something different.

This post covers three areas: getting authorization right, background delivery, and interpreting the data.

1. Authorization: the most commonly botched part

HealthKit access needs user permission. Apple is strict here because health data is sensitive. The most frequent mistakes:

Mistake 1: asking for every permission at once.

Wrong move: the first time the app opens, show an alert requesting 15 different HealthKit data types. The user is overwhelmed and hits “Don’t Allow”.

Right move: ask for permission when the user reaches the related feature. Tapping into “Sleep tracking”? Request sleepAnalysis. In the “Activity” section? Ask for step count. Just-in-time permission.

Mistake 2: confusing read and write permission checks.

HKHealthStore.authorizationStatus(for:)

This function only returns an accurate answer for WRITE permission. For read permission, Apple gives you nothing (privacy). You cannot directly ask whether the user granted read access.

Workaround: query the data. If the result is empty and you know earlier queries returned results, the user may have revoked permission. There’s no straightforward way to check other than via the query itself.

Mistake 3: generic description strings in Info.plist.

You’ll get rejected in App Store review. Instead of “This app reads your health data”, be specific: “ByteBreak uses your step count to help you hit your daily movement goal.”

Mistake 4: no recovery plan after permission denied.

User denied permission, the feature doesn’t work. What do you do? Silently lose them, or tell them “go to Settings and enable it”?

After every permission check I show a graceful fallback UI: “HealthKit permission isn’t granted, would you like to enable it in Settings?” with a direct deep link to Settings.

2. Background delivery

One of HealthKit’s best features: while the user is asleep, you can process data coming from their Apple Watch in the background. Setup is tricky though.

HKObserverQuery: a query whose callback fires whenever the data changes. It works in the background too.

let query = HKObserverQuery(
    sampleType: HKQuantityType(.stepCount),
    predicate: nil
) { _, completionHandler, error in
    // New step data has arrived
    // Do the work, then call completion
    completionHandler()
}
healthStore.execute(query)
healthStore.enableBackgroundDelivery(
    for: HKQuantityType(.stepCount),
    frequency: .immediate
) { _, _ in }

Watch out: once you call enableBackgroundDelivery, iOS wakes your app on HealthKit changes. If you don’t register the observer query on launch, iOS punishes you with “I’m stopping your delivery”.

I made that mistake on ByteBreak: no user-notification permission, background delivery active, but I wasn’t registering the observer query in application(_:didFinishLaunchingWithOptions:). Two weeks later the data stopped coming. iOS had suspended delivery.

Fix: always register observer queries on app launch (when permission exists). Add “health” to UIBackgroundModes while you’re at it.

3. Interpreting data: unit hell

HealthKit’s units get confusing. .count(), .kilocalorie(), .meter(), .gram(). Do you read step count as “steps” or as some other unit?

You have to check the unit on every HKQuantitySample:

let sample: HKQuantitySample = ...
let steps = sample.quantity.doubleValue(for: HKUnit.count())
let calories = sample.quantity.doubleValue(for: HKUnit.kilocalorie())

Reading with the wrong unit doesn’t produce a compile error, it crashes at runtime.

What I learnt in Dentii:

  1. Is brushing time in minutes or seconds? Apple’s “toothbrushing” sample is stored in seconds but that isn’t documented. My first implementation assumed minutes, so I displayed data 60x off.
  2. Timezones get mixed up. A user flies from Ankara to Frankfurt and the data comes back in a mix of two timezones. Which timezone do startDate and endDate use? HKSample has no sourceTimeZone, you have to roll your own solution.
  3. Aggregate queries behave differently. You pull a daily average with HKStatisticsCollectionQuery, but when sources diverge (Apple Watch vs iPhone vs manual) how does the merge happen? Apple controls it with “.strictStartDate” or “.strictEndDate”, but the documentation is thin.

Example: the sleep-tracking flow

How I handle sleep data in Snoozio:

  1. First launch: ask for sleep-data permission.
  2. Check for previously-arrived data (last 30 days).
  3. Enable background delivery, register the observer query.
  4. Through the night, sleep samples from the Apple Watch trigger the observer.
  5. On every trigger, analyse the last 24 hours of sleep data.
  6. Write the analysis to local storage and CloudKit.
  7. When the user opens the app in the morning, the detailed view is ready.

The whole thing happens without the user doing anything. The hardest part is making sure the observer query actually runs in the background.

Privacy details

HealthKit takes privacy very seriously:

  • HealthKit data is not included in iCloud backups. If the user loses their device, the data goes with it.
  • HealthKit access from extensions is limited. No direct access from widgets or share extensions, you need the app context.
  • App Store review scrutinises HealthKit usage closely. Privacy manifest, usage description, and actual implementation all have to line up.
  • A Watch app doesn’t automatically inherit authorization from the iPhone app. Each has to be authorized separately.

Testing

HealthKit testing in the simulator is rough. Use real devices and a real Apple Watch. My approach:

  1. Fake-data injection. Enter data manually in the Health app and see how the app reacts.
  2. Permission edge cases. Grant, revoke, partial. Does the app behave correctly in each?
  3. Background-delivery stress test. Three days of real Apple Watch use.
  4. Device handoff. Same iCloud account on multiple devices, does sync work?

Takeaway

HealthKit is powerful but full of traps. Get authorization right, be disciplined about background delivery, careful with units. The first project you waste three or four days debugging, the second takes half the time, by the third you’ve internalised the patterns.

Biggest recommendation: privacy-first. Ask for as few permissions as possible and be clear why you need them. HealthKit data is among the most sensitive data types in the world, acting responsibly is both legal and ethical.

Have a project on this topic?

Leave a brief summary — I’ll get back to you within 24 hours.

Get in touch