Why Is HealthKit Returning No Data?
Last verified October 2, 2026 · 8 min read
Sources checked (14) · October 2, 2026
- developer.apple.com/documentation/healthkit/hkhealthstore/authorizationstatus(for:) — read permission hidden; denied reads look empty; share permission without read shows only your own data
- developer.apple.com/documentation/healthkit/hkauthorizationstatus — notDetermined, sharingDenied, sharingAuthorized; "only the data that it has written" quote
- developer.apple.com/documentation/healthkit/authorizing-access-to-health-data — limited history window; permissions changeable in Settings or Health; Sources tab
- developer.apple.com/documentation/healthkit/hkhealthstore/earliestauthorizedsampledate(for:) — iOS 27+ limited-window date; treat earlier data as unknown
- developer.apple.com/documentation/healthkit/setting-up-healthkit — isHealthDataAvailable and the errors other methods return
- developer.apple.com/documentation/healthkit/hkhealthstore/ishealthdataavailable() — no HealthKit data on iPadOS 16 and earlier or macOS
- developer.apple.com/documentation/xcode/configuring-healthkit-access — capability requirement; Xcode names of the purpose-string keys
- developer.apple.com/documentation/healthkit/hkhealthstore/requestauthorization(toshare:read:) — crash without usage keys
- developer.apple.com/documentation/healthkit/hkhealthstore/save(_:withcompletion:)-6fmtg — save errors: errorAuthorizationNotDetermined, errorAuthorizationDenied
- developer.apple.com/documentation/healthkit/hkerror/code/errorauthorizationdenied — save-only error; unauthorized apps still receive their own data
- developer.apple.com/documentation/healthkit/hksamplequery/init(sampletype:predicate:limit:sortdescriptors:resultshandler:) — nil predicate returns all samples; HKObjectQueryNoLimit
- developer.apple.com/documentation/healthkit/hkqueryoptions — strictStartDate and strictEndDate
- developer.apple.com/documentation/healthkit/hkhealthstore/enablebackgrounddelivery(for:frequency:withcompletion:) — entitlement requirement; no background queries in the Simulator
- developer.apple.com/documentation/healthkit — users view and manage all their data in the Health app
Covered here:HealthKitHealth Connect
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 (apart from samples your own app saved). 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 about read access. A query against a type the user blocked returns the same empty array as a type with zero samples.
- One asymmetry matters for debugging: without read permission, Apple says "your app will receive only the data that it has written to HealthKit." Your own samples always come back.
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 hardest cause 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. Apple says a missing key crashes the app at the authorization request, so data never flows. - HealthKit capability not enabled — Apple: "To enable your app to access the user's HealthKit store, you must add the HealthKit capability" (which adds the
com.apple.developer.healthkitentitlement). - No store, or no data, where you are testing — HealthKit data isn't available on iPadOS 16 and earlier or on macOS, where
isHealthDataAvailable()returns false; and an empty Simulator or test-device store says nothing about a real user's data. Apple also says background queries aren't supported in the Simulator. - 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).
- A limited history window — Apple's authorization flow lets a person grant access to only a recent window of their history; older samples then don't come back. From iOS 27,
earliestAuthorizedSampleDate(for:)tells you where that window starts. - 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 {
// No HealthKit store on this device (e.g. iPadOS 16 or earlier, or macOS)
return
}
If this returns false, stop — Apple says other HealthKit methods then fail with errorHealthDataUnavailable (or errorHealthDataRestricted where HealthKit is restricted, for example in an enterprise environment), so no query will work. 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 "Privacy - Health Update Usage Description"). Apple's requestAuthorization reference is blunt: "You must set the usage keys, or your app will crash when you request authorization." Grep the built app's Info.plist for both strings; if the key you need is absent, no data ever arrives.
Step 3 — Check the WRITE status (the observable half)#
You cannot query read status, but if you asked to share (write) the type, the share side shows whether the user has answered:
let type = HKQuantityType(.stepCount)
switch healthStore.authorizationStatus(for: type) {
case .notDetermined:
// Share access was never requested for this type — request it first
break
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).
break
case .sharingAuthorized:
// Write allowed — proceed to the write-then-read test
break
@unknown default:
break
}
.notDetermined means you have never requested share permission for this type. That is expected if you only asked to read it — include the type in toShare when you call requestAuthorization(toShare:read:) if you want this check, and the write-then-read test below, to work.
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 a denied read on the real data, a limited history window, or the user genuinely has no data. Ask the user to check your app's access in the Settings app or the Health app — Apple says a person can change your app's permissions at any time in either.
- The write succeeds but reading that same sample back returns empty — this is not read denial: Apple says an app without read permission still receives the data it saved itself. Suspect the query (predicate, date range, or anchor) and move to Step 5.
- The write itself fails — read the error: Apple documents
errorAuthorizationNotDeterminedwhen you never requested share permission for the type anderrorAuthorizationDeniedwhen the user denied it; otherwise go back over the capability and 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. On iOS 27 and later, also compare your start date with earliestAuthorizedSampleDate(for:): Apple says to treat data before a limited window's start as unknown, not absent.
Step 6 — Test on a real device with real data#
If you are still empty on a Simulator or a fresh test device, treat it as inconclusive — an empty store there says nothing about a real user's data. Install the app on a physical device that has samples for that type (Apple notes people can view and manage all of their data in the Health app, so confirm the type has data there), then re-run Steps 4–5. Test background delivery on a device too: Apple says background queries aren't supported in the Simulator.
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 — on iOS 15 and later Apple says it fails with errorAuthorizationDenied when the entitlement is missing — and that 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 you requested share access and the user allowed it)?- Write-then-read round-trips? If yes, the remaining empty read is a denied read, a limited history window, or genuinely no data — have the user check your app's access in Settings or the Health app.
- On iOS 27 and later, does
earliestAuthorizedSampleDate(for:)return a date after the samples you expect? - Data visible for that exact type in the Health app on a real device?
- Predicate widened to
nilstill empty? Then it is permission, a limited window, 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 — except for samples your own app saved, which stay readable. 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?
- Apple documents one hard limit: background queries aren't supported in the Simulator, so background delivery has to be tested on a device. For reads, an empty result in the Simulator cannot tell a bug from an empty store. Confirm on a real device that has samples for the type 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. Apple says a person can change your app's permissions at any time in the Settings app or the Health app, so point the user there; your app appears in the Health app's Sources list once it has requested authorization, even if the user allowed nothing.
Keep reading
From the blog
Findings counted out of this site’s own datasets.
- The HealthKit Error That Never FiresHealthKit defines 17 error cases. The authorization-denied one is documented for saving, not reading — which is why an empty read is so hard to debug.
Elsewhere on the site
Pages that share this one’s concepts and sources, from other sections.
Next steps
Was this page useful?
Independent comparison, last reviewed October 2, 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