Updating SwiftUI from HealthKit Query

412 Views Asked by At

I want to output the variable 'healthStore.valueTest' via ContentView in SwiftUI.

The class healtStore is structured as follows:

class HealthStore {
    
    var healthStore: HKHealthStore?
    var query: HKStatisticsQuery?
    var valueTest: HKQuantity?
    
    
    init() {
        if HKHealthStore.isHealthDataAvailable() {
            healthStore = HKHealthStore()
        }
    }
    
    func calculateBloodPressureSystolic() {
        
        guard let bloodPressureSystolic = HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic) else {
            // This should never fail when using a defined constant.
            fatalError("*** Unable to get the bloodPressure count ***")
        }

        
//        let startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date())
//        let anchorDate = Date.mondayAt12AM()
//        let daily = DateComponents(day: 1)
//        let predicate = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: .strictStartDate)
        
        query = HKStatisticsQuery(quantityType: bloodPressureSystolic,
                                                quantitySamplePredicate: nil,
                                                options: .discreteAverage) {
                                                query, statistics, error in
            
                                                DispatchQueue.main.async{
                                                    self.valueTest = statistics?.averageQuantity()
                                                }

                                                    
        }
        
        healthStore!.execute(query!)
        
    }
}

ContentView is built as follows:

import SwiftUI
import HealthKit

struct ContentView: View {
    
    private var healthStore: HealthStore?
               
    init() {
        healthStore = HealthStore()
    }
    
    
    var body: some View {
        Text("Hello, world!")
            .padding().onAppear(){
                
        if let healthStore = healthStore {
            healthStore.requestAuthorization { success in
                if success {
                    healthStore.calculateBloodPressureSystolic()
                    print(healthStore.query)
                    print(healthStore.valueTest)
                    }
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

The value for the variable self.valueTest is assigned in the process DispatchQueue.main.async. Nevertheless, I get only a nil back when querying via ContentView.

1

There are 1 best solutions below

1
On

You could set up your HealthStore class and use it as an EnvironmentObject. Assuming your app uses the SwiftUI lifecycle you can inject HealthStore into the environment in the @main entry point of your app.

import SwiftUI

@main
struct NameOfYourHeathApp: App {

    let healthStore = HealthStore()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(healthStore)
        }
    }
}

Change your HealthStore class to this. (I removed your commented out code in my sample below)

import HealthKit

class HealthStore: ObservableObject {

    var healthStore: HKHealthStore?
    var query: HKStatisticsQuery?
    var valueTest: HKQuantity?

    init() {
        if HKHealthStore.isHealthDataAvailable() {
            healthStore = HKHealthStore()
        }
    }

    // I moved the HealthStore conditional check out of your View logic 
    // and placed it here instead.
    func setUpHealthStore() {
        let typesToRead: Set = [
            HKQuantityType.quantityType(forIdentifier: .bloodPressureSystolic)!
        ]

        // I left the `toShare` as nil as I did not dig into adding bloodpressure reading to HealthKit.
        healthStore?.requestAuthorization(toShare: nil, read: typesToRead, completion: { success, error in
            if success {
                self.calculateBloodPressureSystolic()
            }
        })

    }

    func calculateBloodPressureSystolic() {
        guard let bloodPressureSystolic = HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic) else {
            // This should never fail when using a defined constant.
            fatalError("*** Unable to get the bloodPressure count ***")
        }
        query = HKStatisticsQuery(quantityType: bloodPressureSystolic,
                                  quantitySamplePredicate: nil,
                                  options: .discreteAverage) {
            query, statistics, error in

            DispatchQueue.main.async{
                self.valueTest = statistics?.averageQuantity()
            }
        }

        healthStore!.execute(query!)
    }
}

Then use it in your ContentView like this.

import SwiftUI

struct ContentView: View {

    @EnvironmentObject var healthStore: HealthStore

    var body: some View {
        Text("Hello, world!")
            .onAppear {
                healthStore.setUpHealthStore()
            }
    }
}

I didn't go through the trouble of setting up the proper permissions in the .plist file, but you'll also need to set up the Health Share Usage Description as well as Health Update Usage Description. I assume you have already done this but I just wanted to mention it.