SwiftUI - HealthKit 中的 DispatchQueue.main.async

Posted

技术标签:

【中文标题】SwiftUI - HealthKit 中的 DispatchQueue.main.async【英文标题】:SwiftUI - DispatchQueue.main.async in HealthKit 【发布时间】:2021-05-24 09:46:37 【问题描述】:

我想在 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() 调用的。在第一次调用时,两个变量都为零。只有当我通过Test按钮调用该函数时,才会在控制台中输出正确的值。

变量 healthStore.systolicValue 和 healthStore.diastolicValue 在 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 的正确值?

【问题讨论】:

【参考方案1】:

这是我用于测试并为我工作的完整代码, 使用 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 错误:可选(错误域=com.apple.healthkit 代码=11“指定谓词没有可用数据。” UserInfo=NSLocalizedDescription=指定谓词没有可用数据。)

----> 计算血压收缩压:可选(1.2())

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

----> calculateBloodPressureDiastolic 错误:可选(错误域=com.apple.healthkit Code=11“指定谓词没有可用数据。” UserInfo=NSLocalizedDescription=指定谓词没有可用数据。)

---->calculateBloodPressureDiastolic:可选(3.4())

UI 显示:

收缩值:1.2 ()

舒张压值:3.4 ()

【讨论】:

【参考方案2】:

我想你快到了。您的 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?   //  <----

    ...
    

【讨论】:

【参考方案3】:

@workindog 我按照建议更改了我的代码,但我仍然没有从 healthStore 获得计算值。

@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()
            
        
    

【讨论】:

【参考方案4】:

您确定数据可用吗?要检查,你能把它放在 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)")
    

diastolicValue 也是如此。

【讨论】:

这是我的输出:----> calculateBloodPressureSystolic 统计:可选( HKQuantityTypeIdentifierBloodPressureSystolic 的统计(2021-05-05 19:17:00 +0000 - 2021-05- 05 21:59:00 +0000) 过源 ((null))>) ----> calculateBloodPressureSystolic 错误:nil ----> calculateBloodPressureSystolic:可选(1.2())【参考方案5】:

我发现了我的错误:

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

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

感谢您的帮助。

【讨论】:

很高兴听到您找到了原因。请把答案标记为正确,谢谢。

以上是关于SwiftUI - HealthKit 中的 DispatchQueue.main.async的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI Healthkit 血压查询

SwiftUI Healthkit 血压查询

从 HealthKit 查询更新 SwiftUI

从 HealthKit 查询更新 SwiftUI

SwiftUI HealthKit 基础教程

SwiftUI + MVVM + DI