SwiftUI - HealthKit 中的 DispatchQueue.main.async

SwiftUI - DispatchQueue.main.async in HealthKit

我想在 SwiftUI 中加载以下 GUI:

import SwiftUI

struct ContentView: View {
    
    @ObservedObject var test = Test()
    @ObservedObject var healthStore = HealthStore()
    
    
    func callUpdate() {
        
        print(test.value)
        print(healthStore.systolicValue)
        print(healthStore.diastolicValue)
    }
    
    var body: some View {
        Text("Platzhalter")
            .padding()
            .onAppear(perform: {
            healthStore.setUpHealthStore()
                callUpdate()
            })
        
        Button("Test"){
            callUpdate()
        }
    }
}

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

变量healthStore.systolicValue 和healthStore.diastolicValue 是通过函数callUpdate() 调用的。在第一次调用时,两个变量都为零。只有当我通过测试按钮调用函数时,控制台才会输出正确的值。

变量 healthStore.systolicValue 和 healthStore.diastolicValue 在 class HealthStore 中计算:

import Foundation
import HealthKit

class HealthStore: ObservableObject {

    var healthStore: HKHealthStore?
    var query: HKStatisticsQuery?
    
    public var systolicValue: HKQuantity?
    public var diastolicValue: HKQuantity?

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

    func setUpHealthStore() {
        let typesToRead: Set = [
            HKQuantityType.quantityType(forIdentifier: .bloodPressureSystolic)!,
            HKQuantityType.quantityType(forIdentifier: .bloodPressureDiastolic)!
        ]

        healthStore?.requestAuthorization(toShare: nil, read: typesToRead, completion: { success, error in
            if success {
                print("requestAuthrization")
                self.calculateBloodPressureSystolic()
                self.calculateBloodPressureDiastolic()
            }
        })

    }

    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.systolicValue = statistics?.averageQuantity()
            }
        }

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

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

        healthStore!.execute(query!)
    }
    
    
}

我需要如何修改我的代码才能在调用 ContentView 时直接获取 healthStore.systolicValue 和 healthStore.diastolicValue 的正确值?

我想你快到了。您的 HealthStore 需要发布新值 收缩压值和舒张压值。不需要“callUpdate()”函数。

我会按如下方式修改您的代码:

(注意一旦计算出 systolicValue 和 diastolicValue,模型会更新,视图也会自动更新)

struct ContentView: View {
    @ObservedObject var healthStore = HealthStore()
    
    var body: some View {
        VStack {
            Text("Platzhalter")
            Text("systolicValue: \(healthStore.systolicValue)")
            Text("diastolicValue: \(healthStore.diastolicValue)")
        }.onAppear {
            healthStore.setUpHealthStore()
        }
    }
}

class HealthStore: ObservableObject {

    var healthStore: HKHealthStore?
    var query: HKStatisticsQuery?
    
    @Published var systolicValue: HKQuantity?    //  <----
    @Published var diastolicValue: HKQuantity?   //  <----

    ...
    
}

您确定数据可用吗?要检查一下,你能把这个放在 HealthStore 中吗:

    DispatchQueue.main.async{
      //  self.systolicValue = statistics?.averageQuantity()
        self.systolicValue = HKQuantity(unit: HKUnit(from: ""), doubleValue: 1.2)
        print("----> calculateBloodPressureSystolic statistics: \(statistics)")
        print("----> calculateBloodPressureSystolic error: \(error)")
        print("----> calculateBloodPressureSystolic: \(self.systolicValue)")
    }

舒张压值也类似。

这是我用于测试并为我工作的完整代码, 使用 macos 11.4,xcode 12.5,目标 ios 14.5,在 iPhone 设备上测试。 如果这对您不起作用,请告诉我们。

import SwiftUI
import HealthKit

@main
struct TestErrorApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class HealthStore: ObservableObject {
    var healthStore: HKHealthStore?
    var query: HKStatisticsQuery?
    @Published var systolicValue: HKQuantity?
    @Published var diastolicValue: HKQuantity?
    
    init() {
        if HKHealthStore.isHealthDataAvailable() {
            healthStore = HKHealthStore()
        }
    }

    func setUpHealthStore() {
        let typesToRead: Set = [
            HKQuantityType.quantityType(forIdentifier: .bloodPressureSystolic)!,
            HKQuantityType.quantityType(forIdentifier: .bloodPressureDiastolic)!
        ]
        healthStore?.requestAuthorization(toShare: nil, read: typesToRead, completion: { success, error in
            if success {
                print("--> requestAuthorization")
                self.calculateBloodPressureSystolic()
                self.calculateBloodPressureDiastolic()
            }
        })
    }
    
    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.systolicValue = statistics?.averageQuantity()
                self.systolicValue = HKQuantity(unit: HKUnit(from: ""), doubleValue: 1.2)
                print("----> calculateBloodPressureSystolic statistics: \(statistics)")
                print("----> calculateBloodPressureSystolic error: \(error)")
                print("----> calculateBloodPressureSystolic: \(self.systolicValue)")
            }
        }
        healthStore!.execute(query!)
    }
    
    func calculateBloodPressureDiastolic() {
        guard let bloodPressureDiastolic = HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic) else {
            // This should never fail when using a defined constant.
            fatalError("*** Unable to get the bloodPressure count ***")
        }
        query = HKStatisticsQuery(quantityType: bloodPressureDiastolic,
                                  quantitySamplePredicate: nil,
                                  options: .discreteAverage) {
            query, statistics, error in
            DispatchQueue.main.async{
               // self.diastolicValue = statistics?.averageQuantity()
                self.diastolicValue = HKQuantity(unit: HKUnit(from: ""), doubleValue: 3.4)
                print("----> calculateBloodPressureDiastolic statistics: \(statistics)")
                print("----> calculateBloodPressureDiastolic error: \(error)")
                print("----> calculateBloodPressureDiastolic: \(self.diastolicValue)")
            }
        }
        healthStore!.execute(query!)
    }
}

struct ContentView: View {
    @ObservedObject var healthStore = HealthStore()
    var bloodPressureStandard = HKQuantity(unit: HKUnit(from: ""), doubleValue: 0.0)
    
    var body: some View {
        VStack {
            Text("systolicValue: \(healthStore.systolicValue ?? bloodPressureStandard)")
            Text("diastolicValue: \(healthStore.diastolicValue ?? bloodPressureStandard)")
        }.onAppear {
            healthStore.setUpHealthStore()
        }
    }
}

这是我得到的输出:

--> 请求授权

----> 计算血压收缩统计数据:无

----> calculateBloodPressureSystolic error: Optional(Error Domain=com.apple.healthkit Code=11 “没有可用于指定谓词的数据。”UserInfo={NSLocalizedDescription=没有可用于指定谓词的数据。} )

----> calculateBloodPressureSystolic: Optional(1.2 ())

----> 计算血压舒张压统计数据:无

----> calculateBloodPressureDiastolic error: Optional(Error Domain=com.apple.healthkit Code=11 “没有可用于指定谓词的数据。”UserInfo={NSLocalizedDescription=没有可用于指定谓词的数据。} )

----> calculateBloodPressureDiastolic: Optional(3.4())

并且 UI 显示:

收缩值:1.2 ()

舒张压值:3.4 ()

我发现了我的错误:

我在 class HealthStore 中声明变量 systolicValue 和 diastolicValue 是错误的。我将它们声明为 public 而不是 @Published。正确的代码是:

@Published var systolicValue: HKQuantity?
@Published var diastolicValue: HKQuantity?

谢谢你的帮助。