
Reading data from HealthKit in a SwiftUI app
Learn how to access and read data stored in the Health app in a SwiftUI app.
Apple’s HealthKit framework allows developers to access health and fitness data stored on a user’s iPhone or Apple Watch. Developers can access this data to create practical applications that creates additional value to our products.
In this tutorial, we’ll build a complete SwiftUI app that reads step count, active energy burned, and heart rate.
By the end of this tutorial, you will have created a fully functional SwiftUI app that requests authorization to access HealthKit data, fetches today’s step count and calories burned, along with the most recent heart rate, and displays this information in a clean and simple UI.
Before we start
To follow this tutorial, you need a basic understanding of SwiftUI and be comfortable writing code using the Swift programming language. We will start from an empty project in SwiftUI for iOS.
Since privacy is a major concern for Apple, especially for accessing health information, we need to add the HealthKit capability to our Xcode project:

Health information is sensitive. To be able to access it you must request user authorization. In the Info tab of the app settings, add a new field called “Privacy - Health Share Usage Description”, with a textual description explaining why our app requires this permission.
Step 1 - Managing authorization and data fetching
The first step is to create a Swift class that will manage authorization requests and data retrieval from the HealthKit framework. We'll define a new file named HealthKitManager.swift
, which contains a singleton class to encapsulate all HealthKit-related logic, including requesting permissions and fetching health data.
import Foundation
import HealthKit
@MainActor
class HealthKitManager {
// 1.
static let shared = HealthKitManager()
private let healthStore = HKHealthStore()
private init() {}
// 2.
func requestAuthorization() async throws -> Bool {
// Ensure HealthKit is available on this device
guard HKHealthStore.isHealthDataAvailable() else { return false }
// Define the types we want to read
let readTypes: Set<HKObjectType> = [
HKObjectType.quantityType(forIdentifier: .stepCount)!,
HKObjectType.quantityType(forIdentifier: .heartRate)!,
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!
]
return try await withCheckedThrowingContinuation { continuation in
healthStore.requestAuthorization(toShare: [], read: readTypes) { success, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: success)
}
}
}
}
// 3.
func fetchMostRecentSample(for identifier: HKQuantityTypeIdentifier) async throws -> HKQuantitySample? {
// Get the quantity type for the identifier
guard let quantityType = HKObjectType.quantityType(forIdentifier: identifier) else {
return nil
}
// Query for samples from start of today until now, sorted by end date descending
let predicate = HKQuery.predicateForSamples(
withStart: Calendar.current.startOfDay(for: Date()),
end: Date(),
options: .strictStartDate
)
let sortDescriptor = NSSortDescriptor(
key: HKSampleSortIdentifierEndDate,
ascending: false
)
return try await withCheckedThrowingContinuation { continuation in
let query = HKSampleQuery(
sampleType: quantityType,
predicate: predicate,
limit: 1,
sortDescriptors: [sortDescriptor]
) { _, samples, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: samples?.first as? HKQuantitySample)
}
}
healthStore.execute(query)
}
}
}
- We start by creating two important properties inside the
HealthKitManager
class:- A singleton instance (
shared
) that provides a globally accessible reference to this manager across the app. - An instance of
HKHealthStore
, which is the main interface for interacting with the HealthKit framework, including requesting authorization and querying health data.
- A singleton instance (
- In the
requestAuthorization()
method, we first ensure that HealthKit is available on the device. We then specify the types of health data we want to read such as step count, heart rate, and active energy burned. Using HealthKit's authorization API, we prompt the user to grant permission, and handle the result asynchronously. - After authorization is granted, the
fetchMostRecentSample()
method is defined to retrieve the most recent data entry for a specific HealthKit quantity type (e.g., steps or heart rate). To do this, we construct anHKSampleQuery
, which requires:- The quantity type to query (e.g.,
.stepCount
). - A predicate to filter results (e.g., from the start of the day to now).
- A sort descriptor to order results by end date in descending order, so the most recent sample is returned.
- The quantity type to query (e.g.,
Step 2 - Define the ViewModel
Now that we’ve defined our HealthKitManager
, the next step is to create a new file called HealthDataViewModel.swift
. This class will serve as a bridge between the view layer and the data layer, managing the health data state and handling user authorization flow.
import Foundation
import HealthKit
import Observation
@MainActor
@Observable class HealthDataViewModel {
// 1.
var stepCount: Double = 0
var heartRate: Double = 0
var activeEnergy: Double = 0
var isAuthorized: Bool = false
var errorMessage: String?
init() {
Task { await requestAuthorization() }
}
// 2.
func requestAuthorization() async {
do {
let success = try await HealthKitManager.shared.requestAuthorization()
self.isAuthorized = success
if success {
await fetchAllHealthData()
}
} catch {
self.errorMessage = error.localizedDescription
}
}
// 3.
func fetchAllHealthData() async {
async let steps: () = fetchStepCount()
async let rate: () = fetchHeartRate()
async let energy: () = fetchActiveEnergy()
_ = await (steps, rate, energy)
}
// 4.
func fetchStepCount() async {
if let sample = try? await HealthKitManager.shared.fetchMostRecentSample(for: .stepCount) {
let value = sample.quantity.doubleValue(for: HKUnit.count())
self.stepCount = value
}
}
func fetchHeartRate() async {
if let sample = try? await HealthKitManager.shared.fetchMostRecentSample(for: .heartRate) {
let value = sample.quantity
.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute()))
self.heartRate = value
}
}
func fetchActiveEnergy() async {
if let sample = try? await HealthKitManager.shared.fetchMostRecentSample(for: .activeEnergyBurned) {
let value = sample.quantity.doubleValue(for: HKUnit.kilocalorie())
self.activeEnergy = value
}
}
}
- In the
HealthDataViewModel
we define all the properties to store the latest values for step count, heart rate, and active energy burned. - When the view model is initialized, it immediately attempts to request HealthKit authorization using the requestAuthorization() method. If the permission is granted, it proceeds to fetch the health data.
- The
fetchAllHealthData()
method uses theasync let
syntax to fetch all three health metrics concurrently, improving the efficiency by running the fetch operations in parallel instead of sequentially. - Each fetch method calls the
HealthKitManager
to request the most recent sample for a specific health metric (e.g.,.stepCount
,.heartRate
). Once retrieved, the raw quantity is converted into aDouble
using the appropriate HealthKit unit (e.g.,.count()
,.kilocalorie()
), and the result is stored in the corresponding property.
Step 3 - Display all the information in a SwiftUI view
With the HealthDataViewModel
now managing all authorization and data retrieval from HealthKit, the final step is to present this information to the user in a SwiftUI view. Open the ContentView.swift
file and update it as shown below:
import SwiftUI
struct ContentView: View {
// 1.
@State private var viewModel = HealthDataViewModel()
// 2.
var body: some View {
NavigationStack {
VStack(spacing: 20) {
if let error = viewModel.errorMessage {
Text("Error: \(error)")
.foregroundColor(.red)
} else if viewModel.isAuthorized {
VStack(spacing: 16) {
HealthInfoView(
label: Text("Step Count"),
value: Text("\(Int(viewModel.stepCount)) steps")
)
HealthInfoView(
label: Text("Heart Rate"),
value: Text(String(format: "%.1f bpm", viewModel.heartRate)),
color: .red
)
HealthInfoView(
label: Text("Active Energy"),
value: Text(String(format: "%.1f kcal", viewModel.activeEnergy)),
color: .green
)
}
} else {
ProgressView("Requesting HealthKit authorization...")
.padding()
}
}
.navigationTitle("Health Data")
}
}
}
struct HealthInfoView<Label: View, Value: View>: View {
let label: Label
let value: Value
var color: Color = .orange
var body: some View {
RoundedRectangle(cornerRadius: 25)
.fill(color.gradient)
.frame(width: 200, height: 150)
.overlay {
VStack {
label
value
}
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
.padding()
}
}
}
#Preview {
ContentView()
}
- This view uses a
@State
variable to create and manage an instance ofHealthDataViewModel
, which keeps track of the latest health metrics and authorization state. - The UI adapts based on the app's current state:
- If an error occurred during authorization or data fetching, it displays the error message.
- If authorization is granted, it shows the most recent values for step count, heart rate, and active energy burned.
- If authorization is still pending, a loading message is displayed.
Conclusion
In this tutorial, we learned how to integrate HealthKit into a SwiftUI application using a structured and modular approach. This enables our app to access health-related data. When the app is launched, a system modal prompts the user to grant permission to share their health information. Once permission is granted, the app can access and utilize the requested data.
Here's the final project that you can download with all the code included in the article: