HealthKit enables your app to read and write health data. Learn to integrate with Health app and track wellness metrics.
HealthKit Setup
Enable HealthKit
- 1Go to Project Settings > Signing & Capabilities
- 2Add "HealthKit" capability
- 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
- 1Privacy: Always respect user privacy
- 2Authorization: Request appropriate permissions
- 3Performance: Optimize queries
- 4Background: Use background delivery wisely
- 5UI: Provide clear visualizations
- 6Error Handling: Handle errors gracefully
- 7Testing: Test thoroughly with Health app
- 8Documentation: Document health data usage
HealthKit enables powerful health and fitness tracking!