Skip to content
All essays
iOSMarch 27, 202513 min

Swift HealthKit: Health Data Integration

Integrate with HealthKit to read and write health data. Fitness, nutrition, and wellness tracking.

Ü
Ümit Uz
Mobile & Full Stack Developer

HealthKit enables your app to read and write health data. Learn to integrate with Health app and track wellness metrics.

HealthKit Setup

Enable HealthKit

  1. 1Go to Project Settings > Signing & Capabilities
  2. 2Add "HealthKit" capability
  3. 3Add usage descriptions for data types

Request Authorization

swift
import HealthKit
import SwiftUI

class HealthManager: ObservableObject {
    private let healthStore = HKHealthStore()

    @Published var authorizationStatus: Bool = false

    func requestAuthorization() {
        guard HKHealthStore.isHealthDataAvailable() else {
            print("HealthKit not available")
            return
        }

        // Define data types to read
        let readTypes: Set<HKObjectType> = [
            HKObjectType.quantityType(forIdentifier: .stepCount),
            HKObjectType.quantityType(forIdentifier: .activeEnergyBurned),
            HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning),
            HKQuantityType.quantityType(forIdentifier: .heartRate)
        ]

        // Define data types to write
        let writeTypes: Set<HKSampleType> = [
            HKObjectType.quantityType(forIdentifier: .stepCount),
            HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)
        ]

        healthStore.requestAuthorization(toShare: writeTypes, read: readTypes) { success, error in
            DispatchQueue.main.async {
                self.authorizationStatus = success
                if !success {
                    print("HealthKit authorization failed: \(error?.localizedDescription ?? "")")
                }
            }
        }
    }
}

Read Health Data

Query Health Data

swift
extension HealthManager {
    @Published var stepCount: Int = 0
    @Published var activeEnergyBurned: Double = 0
    @Published var distance: Double = 0

    func fetchRecentSteps() {
        guard let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
            return
        }

        let now = Date()
        let startOfDay = Calendar.current.startOfDay(for: now)

        let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)

        let query = HKStatisticsQuery(
            quantityType: stepType,
            quantitySamplePredicate: predicate,
            options: .cumulativeSum
        ) { query, result, error in
            guard let result = result else {
                print("Error fetching steps: \(error?.localizedDescription ?? "")")
                return
            }

            let steps = result.sumQuantity()?.doubleValue(for: .count()) ?? 0

            DispatchQueue.main.async {
                self.stepCount = Int(steps)
            }
        }

        healthStore.execute(query)
    }

    func fetchHeartRate() {
        guard let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate) else {
            return
        }

        let now = Date()
        let startTime = Calendar.current.date(byAdding: .hour, value: -1, to: now)!

        let predicate = HKQuery.predicateForSamples(withStart: startTime, end: now, options: .strictStartDate)

        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)

        let query = HKSampleQuery(
            sampleType: heartRateType,
            predicate: predicate,
            limit: 1,
            sortDescriptors: [sortDescriptor]
        ) { query, samples, error in
            guard let sample = samples?.first as? HKQuantitySample else {
                print("Error fetching heart rate: \(error?.localizedDescription ?? "")")
                return
            }

            let heartRate = sample.quantity.doubleValue(for: .count())

            DispatchQueue.main.async {
                print("Heart Rate: \(heartRate) BPM")
            }
        }

        healthStore.execute(query)
    }
}

Write Health Data

Save Health Data

swift
extension HealthManager {
    func saveSteps(_ steps: Int, date: Date = Date()) {
        guard let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
            return
        }

        let quantity = HKQuantity(unit: HKUnit.count(), value: Double(steps))

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

        healthStore.save(sample) { success, error in
            if !success {
                print("Error saving steps: \(error?.localizedDescription ?? "")")
            }
        }
    }

    func saveWorkout(
        duration: TimeInterval,
        calories: Double,
        distance: Double,
        date: Date = Date()
    ) {
        let configuration = HKWorkoutConfiguration()
        configuration.activityType = .walking

        let workout = HKWorkout(
            activityType: .walking,
            start: date.addingTimeInterval(-duration),
            end: date
        )

        var totalEnergyBurned = HKQuantity(unit: HKUnit.kilocalorie(), value: calories)
        var totalDistance = HKQuantity(unit: HKUnit.meter(), value: distance)

        let builder = HKWorkoutBuilder(workout: workout)
        builder.duration = duration

        builder.addTotalEnergyBurned(totalEnergyBurned)
        builder.addTotalDistance(totalDistance, unit: .meter())

        builder.finishWorkout { workout, error in
            if let workout = workout {
                self.healthStore.save(workout) { success, error in
                    if !success {
                        print("Error saving workout: \(error?.localizedDescription ?? "")")
                    }
                }
            }
        }
    }
}

Health Queries

Complex Queries

swift
extension HealthManager {
    func fetchStepCountsForWeek() -> [(Date, Int)] {
        var stepCounts: [(Date, Int)] = []

        guard let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
            return []
        }

        let calendar = Calendar.current
        let endDate = Date()
        var startDate = calendar.date(byAdding: .day, value: -7, to: endDate)!

        let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)

        let query = HKStatisticsCollectionQuery(
            quantityType: stepType,
            quantitySamplePredicate: predicate,
            options: .cumulativeSum,
            anchorDate: startDate,
            intervalComponents: DateComponents(day: 1)
        )

        healthStore.execute(query) { query, results, error in
            guard let results = results else {
                return
            }

            for statistics in results {
                if let quantity = statistics.sumQuantity() {
                    let steps = Int(quantity.doubleValue(for: .count()))
                    let date = statistics.startDate
                    stepCounts.append((date, steps))
                }
            }
        }

        return stepCounts
    }

    func fetchAverageHeartRate(for sampleType: HKQuantityType) async throws -> Double {
        let now = Date()
        let startTime = Calendar.current.date(byAdding: .hour, value: -24, to: now)!

        let predicate = HKQuery.predicateForSamples(withStart: startTime, end: now, options: .strictStartDate)

        let query = HKStatisticsQuery(
            quantityType: sampleType,
            quantitySamplePredicate: predicate,
            options: .discreteAverage
        )

        return try await withCheckedThrowingContinuation { continuation in
            healthStore.execute(query) { query, result, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else if let result = result, let quantity = result.averageQuantity() {
                    continuation.resume(returning: quantity.doubleValue(for: .count()))
                } else {
                    continuation.resume(returning: 0)
                }
            }
        }
    }
}

Health Store Observers

Real-time Updates

swift
class HealthObserver: ObservableObject {
    private let healthStore = HKHealthStore()
    private var query: HKObserverQuery?

    @Published var latestStepCount: Int = 0

    func startObservingSteps() {
        guard let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
            return
        }

        let query = HKObserverQuery(sampleType: stepType, predicate: nil) { [weak self] query, samples, error in
            guard let samples = samples as? [HKQuantitySample] else {
                return
            }

            let totalSteps = samples.reduce(0.0) { result, sample in
                result + sample.quantity.doubleValue(for: .count())
            }

            DispatchQueue.main.async {
                self?.latestStepCount = Int(totalSteps)
            }
        }

        healthStore.execute(query)

        self.query = query
    }

    func stopObserving() {
        query?.stop()
        query = nil
    }
}

Workout Sessions

Live Workout

swift
class WorkoutSessionManager: ObservableObject {
    private let healthStore = HKHealthStore()
    private var builder: HKLiveWorkoutBuilder?

    @Published var isRunning = false
    @Published var heartRate: Double = 0
    @Published var activeCalories: Double = 0

    func startWorkout() {
        let configuration = HKWorkoutConfiguration()
        configuration.activityType = .running
        configuration.locationType = .outdoor

        builder = HKLiveWorkoutBuilder(healthStore: healthStore, configuration: configuration, workout: nil)

        builder?.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, workoutConfiguration: configuration)

        builder?.beginCollection { success, error in
            if !success {
                print("Error starting workout: \(error?.localizedDescription ?? "")")
                return
            }

            DispatchQueue.main.async {
                self.isRunning = true
            }
        }
    }

    func endWorkout() {
        builder?.endCollection { workout, error in
            if let workout = workout {
                self.healthStore.save(workout) { success, error in
                    DispatchQueue.main.async {
                        self.isRunning = false
                    }
                }
            }
        }

        builder = nil
    }

    func updateMetrics() {
        // Update workout metrics in real-time
    }
}

Background Delivery

Background Updates

swift
extension HealthManager {
    func enableBackgroundDelivery() {
        guard let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
            return
        }

        let query = HKObserverQuery(sampleType: stepType, predicate: nil) { query, samples, error in
            // Handle background updates
        }

        healthStore.enableBackgroundDelivery(for: stepType, frequency: .immediate)

        healthStore.setObserver(query, updateHandler: { query, samples, error in
            // Handle updates
        })

        healthStore.execute(query)
    }

    func disableBackgroundDelivery() {
        guard let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
            return
        }

        healthStore.disableBackgroundDelivery(for: stepType)
    }
}

Best Practices

  1. 1Privacy: Always respect user privacy
  2. 2Authorization: Request appropriate permissions
  3. 3Performance: Optimize queries
  4. 4Background: Use background delivery wisely
  5. 5UI: Provide clear visualizations
  6. 6Error Handling: Handle errors gracefully
  7. 7Testing: Test thoroughly with Health app
  8. 8Documentation: Document health data usage

HealthKit enables powerful health and fitness tracking!

Related essays

Next essay
Swift SiriKit: Voice Assistant Integration