Saving data in HealthKit in a SwiftUI app

Saving data in HealthKit in a SwiftUI app

Learn how to write health-related information in the Health app in a SwiftUI app.

In a previous article, we discussed how to read data from HealthKit in a SwiftUI app. In this tutorial, we'll see how to request writing permission and save data to HealthKit to modify the number of steps.

Before we start

To get started with this tutorial, it’s recommended to have a basic understanding of SwiftUI and be comfortable writing code in Swift. For this project, we will use the final code from the Reading data from HealthKit in a SwiftUI app article. You can download it here:

Once you have downloaded the project, you need to add a new field in the Info tab of the app settings named “Privacy - Health Update Usage Description”, with a textual description explaining why our app requires this permission.

Step 1 - Setting up HealthKit Permissions

To write health data, we need to define the health parameter we want to modify and pass it when requesting authorization. Go into the HealthKitManager file and modify it as follows:

import Foundation
import HealthKit

@MainActor
class HealthKitManager {
    static let shared = HealthKitManager()
    private let healthStore = HKHealthStore()
    
    private init() {}
    
    /// Requests authorization to read HealthKit data. Returns true on success.
    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)!
        ]
        
        // 1.
        let writeType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
        
        // 2.
        return try await withCheckedThrowingContinuation { continuation in
            healthStore.requestAuthorization(toShare: [writeType], read: readTypes) { success, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: success)
                }
            }
        }
    }

    
    func fetchMostRecentSample(for identifier: HKQuantityTypeIdentifier) async throws -> HKQuantitySample? {
        ...
    }
}
  1. In the example above, we define a new parameter for a specific quantity: steps.
  2. In the requestAuthorization() method we provide the parameter we defined to the toShare.

Step 2 - Inserting Data into HealthKit

Now that we have requested the authorization to modify the steps parameter, we can define a new method named insertSteps() that will modify the number of steps in the Health app.

import Foundation
import HealthKit

@MainActor
class HealthKitManager {
    static let shared = HealthKitManager()
    private let healthStore = HKHealthStore()
    
    private init() {}
    
    func requestAuthorization() async throws -> Bool {
        ...
    }

    
    func fetchMostRecentSample(for identifier: HKQuantityTypeIdentifier) async throws -> HKQuantitySample? {
        ...
    }

    func insertSteps(count: Double, date: Date) {

        // 1.
        guard let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
            print("Step Count Type is unavailable.")
            return
        }

        // 2.
        let quantity = HKQuantity(unit: HKUnit.count(), doubleValue: count)

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

        // 4.
        healthStore.save(sample) { (success, error) in
            if success {
                print("Successfully saved step count to HealthKit.")
            } else {
                if let error = error {
                    print("Failed to save steps: \(error.localizedDescription)")
                }
            }
        }
    }
}
  1. Check if the health data that we want to modify is available.
  2. Define an HKQuantity using the unit .count() and the number of steps (count). This represents how much data will be stored in HealthKit.
  3. Create an HKQuantitySample object that represents a single data point in HealthKit.It includes the type (steps), the quantity (number of steps), and a start/end date (for when the activity occurred).
  4. Commit the changes using the .save() method of the HKHealthStore() class

Step 3 - SwiftUI Example

By modifying our ContentView file, we can easily add a button that will execute the insertSteps() method that we defined in the step before.

import SwiftUI

struct ContentView: View {
    @State var viewModel = HealthDataViewModel()
    
    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) {
                        RoundedRectangle(cornerRadius: 25)
                            .fill(.orange.gradient)
                            .frame(width: 200, height: 150)
                            .overlay {
                                VStack {
                                    Text("Step Count")
                                    Text("\(Int(viewModel.stepCount)) steps")
                                }
                                .font(.title2)
                                .fontWeight(.bold)
                                .foregroundColor(.white)
                                .padding()
                            }
                        
                        RoundedRectangle(cornerRadius: 25)
                            .fill(.red.gradient)
                            .frame(width: 200, height: 150)
                            .overlay {
                                VStack {
                                    Text("Heart Rate")
                                    Text("\(viewModel.heartRate)")
                                }
                                .font(.title2)
                                .fontWeight(.bold)
                                .foregroundColor(.white)
                                .padding()
                            }
                        
                        RoundedRectangle(cornerRadius: 25)
                            .fill(.red.gradient)
                            .frame(width: 200, height: 150)
                            .overlay {
                                VStack {
                                    Text("Active Energy")
                                    Text(String(format: "%.1f kcal", viewModel.activeEnergy))
                                }
                                .font(.title2)
                                .fontWeight(.bold)
                                .foregroundColor(.white)
                                .padding()
                            }
                        
                        // 1.
                        Button("Insert 1000 Steps") {
                            HealthKitManager.shared.insertSteps(count: 1000, date: Date.now)
                            Task {
                                await viewModel.fetchStepCount()
                            }
                            
                        }.padding()
                    }
                } else {
                    ProgressView("Requesting HealthKit authorization...")
                        .padding()
                }
            }
            .navigationTitle("Health Data")
        }
    }
}
  1. By adding a simple button, we can trigger the execution of the insertSteps() method accessing it through the shared instance defined in the HealthKitManager class. After the execution, we update all the values again.

Conclusion

If you run the app on your device, it will prompt you to grant all the necessary authorization for both reading and writing. After that, you will be able to update both on the project and on the Health app.

0:00
/0:26

You can access the final version of the project by downloading the following file: