dpearson2699/swift-ios-skills3 files

Healthkit

Read, 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 HKLiveWorkoutBuilder, HKUnit, and HKQuantityTypeIdentifier values. Use when integrating with Apple Health, displaying health metrics, recording workouts, or enabling background health data delivery.

Specification
Skill ID
dpearson2699/swift-ios-skills/healthkit
Publisher
dpearson2699
Repository
swift-ios-skills
Installs
0
Files
3
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

dpearson2699/swift-ios-skills/healthkitInstalls these files
  • SKILL.md
  • evals/evals.json
  • references/healthkit-patterns.md

What this skill tells the agent

HealthKit

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+.

Contents

  • Setup and Availability
  • Authorization
  • Reading Data: Sample Queries
  • Reading Data: Statistics Queries
  • Reading Data: Statistics Collection Queries
  • Writing Data
  • Background Delivery
  • Workout Sessions
  • Common Data Types
  • HKUnit Reference
  • Common Mistakes
  • Review Checklist
  • References

Setup and Availability

Project Configuration

  1. Enable the HealthKit capability in Xcode (adds the entitlement)
  2. Add NSHealthShareUsageDescription (read) and NSHealthUpdateUsageDescription (write) to Info.plist
  3. For background delivery, enable the "Background Delivery" sub-capability

Availability Check

Always check availability before calling other HealthKit APIs. Health data is available on iOS, watchOS, visionOS, iPadOS 17+, and iOS apps running on Vision Pro. It is unavailable on iPadOS 16 or earlier and may be restricted by managed device policy.

import HealthKit

guard HKHealthStore.isHealthDataAvailable() else {
    // Health data is unavailable or restricted on this device.
    return
}

let healthStore = HKHealthStore()

Create a single HKHealthStore instance and reuse it throughout your app. It is thread-safe. If HealthKit is optional, review Xcode's generated UIRequiredDeviceCapabilities healthkit entry so unsupported devices are not excluded unintentionally.

Authorization

Request only the types your app genuinely needs. App Review rejects apps that over-request.

func requestAuthorization() async throws {
    let typesToShare: Set<HKSampleType> = [
        HKQuantityType(.stepCount),
        HKQuantityType(.activeEnergyBurned)
    ]

    let typesToRead: Set<HKObjectType> = [
        HKQuantityType(.stepCount),
        HKQuantityType(.heartRate),
        HKQuantityType(.activeEnergyBurned),
        HKCharacteristicType(.dateOfBirth)
    ]

    try await healthStore.requestAuthorization(
        toShare: typesToShare,
        read: typesToRead
    )
}

Checking Authorization Status

authorizationStatus(for:) reports write/share authorization. HealthKit does not reveal whether read permission was granted or denied. If the user denies read access, queries return only samples your app successfully saved, which may look like empty or partial data.

let status = healthStore.authorizationStatus(
    for: HKQuantityType(.stepCount)
)

switch status {
case .notDetermined:
    // Haven't requested yet -- safe to call requestAuthorization
    break
case .sharingAuthorized:
    // User granted write access
    break
case .sharingDenied:
    // User denied write access (read denial is indistinguishable from "no data")
    break
@unknown default:
    break
}

Reading Data: Sample Queries

Use HKSampleQueryDescriptor (async/await) for one-shot reads. Prefer descriptors over the older callback-based HKSampleQuery.

func fetchRecentHeartRates() async throws -> [HKQuantitySample] {
    let heartRateType = HKQuantityType(.heartRate)

    let descriptor = HKSampleQueryDescriptor(
        predicates: [.quantitySample(type: heartRateType)],
        sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)],
        limit: 20
    )

    let results = try await descriptor.result(for: healthStore)
    return results
}

// Extracting values from samples:
for sample in results {
    let bpm = sample.quantity.doubleValue(
        for: HKUnit.count().unitDivided(by: .minute())
    )
    print("\(bpm) bpm at \(sample.endDate)")
}

Reading Data: Statistics Queries

Use HKStatisticsQueryDescriptor for aggregated single-value stats (sum, average, min, max).

func fetchTodayStepCount() async throws -> Double? {
    let calendar = Calendar.current
    let startOfDay = calendar.startOfDay(for: Date())
    let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!

    let predicate = HKQuery.predicateForSamples(
        withStart: startOfDay, end: endOfDay
    )
    let stepType = HKQuantityType(.stepCount)
    let samplePredicate = HKSamplePredicate.quantitySample(
        type: stepType, predicate: predicate
    )

    let query = HKStatisticsQueryDescriptor(
        predicate: samplePredicate,
        options: .cumulativeSum
    )

    let result = try await query.result(for: healthStore)
    return result?.sumQuantity()?.doubleValue(for: .count())
}

Options by data type:

  • Cumulative types (steps, calories): .cumulativeSum
  • Discrete types (heart rate, weight): .discreteAverage, .discreteMin, .discreteMax

Reading Data: Statistics Collection Queries

Use HKStatisticsCollectionQueryDescriptor for time-series data grouped into intervals -- ideal for charts.

func fetchDailySteps(forLast days: Int) async throws -> [(date: Date, steps: Double)] {
    let calendar = Calendar.current
    let endDate = calendar.startOfDay(
        for: calendar.date(byAdding: .day, value: 1, to: Date())!
    )
    let startDate = calendar.date(byAdding: .day, value: -days, to: endDate)!

    let predicate = HKQuery.predicateForSamples(
        withStart: startDate, end: endDate
    )
    let stepType = HKQuantityType(.stepCount)
    let samplePredicate = HKSamplePredicate.quantitySample(
        type: stepType, predicate: predicate
    )

    let query = HKStatisticsCollectionQueryDescriptor(
        predicate: samplePredicate,
        options: .cumulativeSum,
        anchorDate: endDate,
        intervalComponents: DateComponents(day: 1)
    )

    let collection = try await query.result(for: healthStore)
    var dailySteps: [(date: Date, steps: Double)] = []

    collection.statisticsCollection.enumerateStatistics(
        from: startDate, to: endDate
    ) { statistics, _ in
        let steps = statistics.sumQuantity()?
            .doubleValue(for: .count()) ?? 0
        dailySteps.append((date: statistics.startDate, steps: steps))
    }

    return dailySteps
}

Long-Running Collection Query

Use results(for:) (plural) to get an AsyncSequence that emits updates as new data arrives:

let updateStream = query.results(for: healthStore)

Task {
    for try await result in updateStream {
        // result.statisticsCollection contains updated data
    }
}

Writing Data

Create HKQuantitySample objects and save them to the store.

func saveSteps(count: Double, start: Date, end: Date) async throws {
    let stepType = HKQuantityType(.stepCount)
    let quantity = HKQuantity(unit: .count(), doubleValue: count)

    let sample = HKQuantitySample(
        type: stepType,
        quantity: quantity,
        start: start,
        end: end
    )

    try await healthStore.save(sample)
}

Treat try await healthStore.save(sample) returning as the save success gate; only then report success or advance app state. On failure, surface the error and correct the known authorization, type, unit, duration, or input problem before constructing another sample. A bounded query or inspection in the Health app is useful as an integration-test check when persistence evidence is required, but is not a mandatory production read after every save.

Your app can only delete samples it created. Samples from other apps or Apple Watch are read-only.

Background Delivery

Register for background updates so your app is launched when new data arrives. Requires the background delivery entitlement.

func enableStepCountBackgroundDelivery() async throws {

Use this skill in a project

Open any RiverX project, search the Skills panel for dpearson2699/swift-ios-skills/healthkit, and install it in one click.

Use this skill