Why Is HealthKit Returning No Data?
Updated July 9, 2026
Your HealthKit query runs, throws no error, and returns an empty array. Here is the single most important thing to know: HealthKit cannot tell you that a read was denied — a blocked read returns the exact same empty result as a type that genuinely has no samples. Apple hides read-authorization state on purpose, so "permission denied" and "no data" are indistinguishable from your code. The fastest way forward is to stop trusting the empty result and isolate the cause with a write-then-read test, because write status is observable.
Why an empty read is ambiguous by design
Apple deliberately withholds read-permission status to avoid leaking that sensitive health data exists. From Apple's authorization docs: to help prevent leaks of sensitive health information, your app cannot determine whether the user granted permission to read data. The practical consequence:
authorizationStatus(for:)reports the share (write) side truthfully —.notDetermined,.sharingDenied, or.sharingAuthorized.- It tells you nothing reliable about read access. A query against a type the user blocked returns the same empty array as a type with zero samples.
So the empty result is not your bug report. You have to infer the cause by ruling out plumbing and then using the write side (which you can observe) as a proxy.
Most likely causes (ranked)
- Read permission was denied — the most common and the hardest to see, because it is invisible by design. Isolate it with the write-then-read test below.
- Missing Info.plist usage-description keys — reading requires
NSHealthShareUsageDescription; writing requiresNSHealthUpdateUsageDescription. Missing the relevant key crashes the app at the authorization request, so data never flows. - HealthKit capability not enabled — without the HealthKit capability (and its
com.apple.developer.healthkitentitlement),HKHealthStorecalls fail. - Running on the iOS Simulator — the Simulator has little or no Health data and inconsistent HealthKit behavior; queries commonly return empty even with correct code.
- The user genuinely has no data for that type — no source app or device ever wrote that quantity or category type (e.g., no VO2 max, no blood glucose).
- Background delivery not set up — for live updates you need
enableBackgroundDelivery(for:frequency:), anHKObserverQuery, and the background-delivery entitlement; without them data looks stale or empty. - Wrong date range or predicate — a query predicate whose
startDate…endDatemisses the samples (time-zone bugs,Date()boundaries,.strictStartDate) returns empty even though data exists.
How to fix it
Step 1 — Confirm the plumbing is alive
Before anything else, verify HealthKit can run at all:
guard HKHealthStore.isHealthDataAvailable() else {
// HealthKit unavailable on this device (e.g. iPad without Health, or Simulator quirks)
return
}
If this returns false, stop — no query will work until it is true. Also confirm the HealthKit capability is added in Signing & Capabilities and the entitlement is present in the signed build.
Step 2 — Verify both Info.plist keys exist
Reads need NSHealthShareUsageDescription; writes need NSHealthUpdateUsageDescription (Xcode labels these "Privacy – Health Share Usage Description" and "…Update Usage Description"). A missing key produces a crash log at requestAuthorization. Grep the built app's Info.plist for both strings; if the key you need is absent, the request silently fails or crashes and no data ever arrives.
Step 3 — Check the WRITE status (the observable half)
You cannot query read status, but you can confirm the permission sheet was actually presented by checking the share side:
let type = HKQuantityType(.stepCount)
switch healthStore.authorizationStatus(for: type) {
case .notDetermined:
// The permission sheet was never shown — call requestAuthorization first
case .sharingDenied:
// Write is denied. This says NOTHING about read: read and write authorization
// are independent, and read state is invisible by design (see below).
case .sharingAuthorized:
// Sheet was shown and write allowed — proceed to the write-then-read test
@unknown default:
break
}
.notDetermined means you never requested authorization — fix that first with requestAuthorization(toShare:read:).
Step 4 — Isolate read-denial with a write-then-read test
This is the core diagnostic. Because write status is observable, writing a throwaway sample and immediately reading it back tells you exactly where the failure is. Write a sample of the target type with HKHealthStore.save(_:), then run an HKSampleQuery for that same type:
- The sample round-trips back — your auth, capability, and query are all correctly wired. The original emptiness is either a denied read on the real data or the user genuinely has no data. Send the user to Settings → Privacy & Security → Health → [your app] to inspect and toggle read access.
- The write succeeds but the read still returns empty — the read side is denied (invisible) or your predicate is wrong. Move to Step 5.
- The write itself fails — the problem is upstream: capability, entitlement, or Info.plist (Steps 1–3).
Step 5 — Widen the predicate to rule out a query bug
Temporarily remove the filter to see whether the data exists at all:
let query = HKSampleQuery(
sampleType: HKQuantityType(.stepCount),
predicate: nil, // no date filter at all
limit: HKObjectQueryNoLimit,
sortDescriptors: nil
) { _, samples, error in
// If samples appear here but not with your real predicate,
// the date range / options were the bug.
}
If data appears with predicate: nil but not with your real predicate, the bug is your startDate…endDate, HKQueryOptions (e.g. .strictStartDate), or an anchored-query anchor — not permissions.
Step 6 — Test on a real device with real data
If you are still empty on the Simulator, treat it as inconclusive. Install the app on a physical device that has samples for that type (open Health app → Browse and confirm the type has data under "Data Sources & Access"), then re-run Steps 4–5.
Step 7 — For live updates, wire background delivery
If the initial read works but data never refreshes while backgrounded, you are missing the update path: call enableBackgroundDelivery(for:frequency:), register an HKObserverQuery, and add the com.apple.developer.healthkit.background-delivery entitlement. Verify enableBackgroundDelivery completed without error and the observer's completion handler is actually being called.
Still stuck? Quick triage checklist
HKHealthStore.isHealthDataAvailable()returnstrue?- HealthKit capability + entitlement in the signed build?
- Both
NSHealthShareUsageDescriptionandNSHealthUpdateUsageDescriptioninInfo.plist? authorizationStatus(for:)on the share side is.sharingAuthorized(proves the sheet was shown)?- Write-then-read round-trips? If yes, the remaining empty read is denied-read or genuinely-no-data — check Settings → Privacy & Security → Health.
- Data visible for that exact type in the Health app on a real device?
- Predicate widened to
nilstill empty? Then it is permission or no-data, not the query.
Remember the load-bearing rule: an empty HealthKit read is never proof of anything by itself. Use the write side and a widened predicate to turn "empty" into an answer.
For the correct end-to-end setup, see the HealthKit integration guide. If you are still deciding between platforms or need to support Android too, compare Apple HealthKit vs Google Health Connect. Health Connect has a different failure mode — see Health Connect no data.
Frequently asked questions
- Can I detect whether the user denied read access in HealthKit?
- No. Apple deliberately hides read-authorization state to avoid leaking that health data exists, so authorizationStatus(for:) only reports the share/write side. A denied read returns the same empty result as a type with no data. Infer it with a write-then-read test.
- Why does authorizationStatus(for:) say authorized but my read is still empty?
- That status reflects write/share permission, not read. The user can allow writing while blocking reading, and HealthKit will not tell you. Confirm real data exists in the Health app and use a widened predicate to rule out a query bug.
- Does HealthKit work on the iOS Simulator?
- Partly, but the Simulator has little or no Health data and inconsistent behavior, so queries often return empty even with correct code. Always confirm on a real device with data before concluding your code is wrong.
- My app crashes when I request HealthKit authorization. Why?
- The most common cause is a missing Info.plist usage-description key: NSHealthShareUsageDescription for reads or NSHealthUpdateUsageDescription for writes. Add the relevant key and the crash at requestAuthorization goes away.
- How do I send the user to fix HealthKit read permissions?
- You cannot toggle read access from code. Direct the user to Settings, then Privacy & Security, then Health, then your app, where they can inspect and enable the specific read types you requested.
Keep reading
Independent comparison, last reviewed July 9, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.
← All troubleshooting · by AIFitnessAPI