Swift HealthKit HKStatisticsCollectionQuery statisticsUpdateHandler 并不总是被调用

Posted

技术标签:

【中文标题】Swift HealthKit HKStatisticsCollectionQuery statisticsUpdateHandler 并不总是被调用【英文标题】:Swift HealthKit HKStatisticsCollectionQuery statisticsUpdateHandler not always called 【发布时间】:2020-11-24 15:50:13 【问题描述】:

我有一个测试项目,我可以在其中获取用户在一周内每天的跌倒总数。 initialResultsHandler 每次都能完美运行,但 statisticsUpdateHandler 并不总是触发。如果您启动应用程序,然后转到健康应用程序并手动插入瀑布,切换回测试应用程序,您应该会看到今天更新的总数。实际上,这大约适用于前 3-6 次。之后,statisticsUpdateHandler 不再被调用。

同样奇怪的是,如果您删除数据然后返回到测试应用程序,或者添加比现在更早的时间的数据,statisticsUpdateHandler 会被调用。这让我认为它与 statisticsUpdateHandler 结束日期有关。

Apple 的文档很清楚,但我担心他们可能会遗漏一些内容。

如果此属性设置为 nil,则统计信息收集查询将在计算完初始结果后立即自动停止。如果此属性不为零,则查询的行为类似于观察者查询。它继续运行,监控 HealthKit 存储。如果任何新的匹配样本被保存到存储中——或者如果任何现有的匹配样本被从存储中删除——查询将在后台队列上执行更新处理程序。

statisticsUpdateHandler 有没有被调用的原因?我在下面包含了一个测试项目。

struct Falls: Identifiable
    let id = UUID()
    let date: Date
    let value: Int
    
    var formattedDate: String
        let formatter = DateFormatter()
        formatter.setLocalizedDateFormatFromTemplate("MM/dd/yyyy")
        return formatter.string(from: date)
    

struct ContentView: View 
    @StateObject var manager = HealthKitManager()
    
    var body: some View 
        NavigationView
            List
                Text("Updates: \(manager.updates)")
                ForEach(manager.falls) falls in
                    HStack
                        Text(falls.value.description)
                        Text(falls.formattedDate)
                    
                
            
            .overlay(
                ProgressView()
                    .scaleEffect(1.5)
                    .opacity(manager.isLoading ? 1 : 0)
            )
            .navigationTitle("Falls")
        
    


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

class HealthKitManager: ObservableObject
    let healthStore = HKHealthStore()
    let fallType   = HKQuantityType.quantityType(forIdentifier: .numberOfTimesFallen)!
    @Published var isLoading = false
    @Published var falls = [Falls]()
    @Published var updates = 0

    init() 
        let healthKitTypesToRead: Set<HKSampleType> = [fallType]

        healthStore.requestAuthorization(toShare: nil, read: healthKitTypesToRead)  (success, error) in
            if let error = error
                print("Error: \(error)")
             else if success
                self.startQuery()
            
        
    
    
    func startQuery()
        let now = Date()
        let cal = Calendar.current
        let sevenDaysAgo = cal.date(byAdding: .day, value: -7, to: now)!
        let startDate = cal.startOfDay(for: sevenDaysAgo)
        let predicate = HKQuery.predicateForSamples(withStart: startDate, end: now, options: [.strictStartDate, .strictEndDate])
        
        var interval = DateComponents()
        interval.day = 1
        
        // start from midnight
        let anchorDate = cal.startOfDay(for: now)
       
        let query = HKStatisticsCollectionQuery(
            quantityType: fallType,
            quantitySamplePredicate: predicate,
            options: .cumulativeSum,
            anchorDate: anchorDate,
            intervalComponents: interval
        )
        
        query.initialResultsHandler =  query, collection, error in
            guard let collection = collection else 
                print("No collection")
                DispatchQueue.main.async
                    self.isLoading = false
                
                return
            
            
            collection.enumerateStatistics(from: startDate, to: Date()) (result, stop) in
                guard let sumQuantity = result.sumQuantity() else 
                    return
                
                
                let totalFallsForADay = Int(sumQuantity.doubleValue(for: .count()))
                let falls = Falls(date: result.startDate, value: totalFallsForADay)
                print(falls.value, falls.formattedDate)
                
                DispatchQueue.main.async
                    self.falls.insert(falls, at: 0)
                
            
            
            print("initialResultsHandler done")
            DispatchQueue.main.async
                self.isLoading = false
            
        
        
        
        query.statisticsUpdateHandler =  query, statistics, collection, error in
            print("In statisticsUpdateHandler...")
            guard let collection = collection else 
                print("No collection")
                DispatchQueue.main.async
                    self.isLoading = false
                
                return
            
            
            DispatchQueue.main.async
                self.isLoading = true
                self.updates += 1
                self.falls.removeAll(keepingCapacity: true)
            
            
            collection.enumerateStatistics(from: startDate, to: Date()) (result, stop) in
                guard let sumQuantity = result.sumQuantity() else 
                    return
                
                
                let totalFallsForADay = Int(sumQuantity.doubleValue(for: .count()))
                let falls = Falls(date: result.startDate, value: totalFallsForADay)
                print(falls.value, falls.formattedDate)
                print("\n\n")

                DispatchQueue.main.async
                    self.falls.insert(falls, at: 0)
                
            
            
            print("statisticsUpdateHandler done")
            DispatchQueue.main.async
                self.isLoading = false
            
        
        
        
        isLoading = true
        healthStore.execute(query)
    

【问题讨论】:

【参考方案1】:

我太专注于 statisticsUpdateHandler 以及开始和结束时间,以至于我没有关注查询本身。事实证明,谓词是问题所在。通过给它一个结束日期,它永远不会在初始谓词结束日期之外寻找样本。

将谓词更改为此解决了问题:

let predicate = HKQuery.predicateForSamples(withStart: startDate, end: nil, options: [.strictStartDate])

【讨论】:

以上是关于Swift HealthKit HKStatisticsCollectionQuery statisticsUpdateHandler 并不总是被调用的主要内容,如果未能解决你的问题,请参考以下文章

HealthKit 仅在 Swift 2.0 中启动一次

使用 Swift 从 HealthKit 读取日期间隔的步骤

从 swift 1.2 迁移后 swift2 中的 healthKit 错误

从 swift 1.2 迁移后 swift2 中的 healthKit 错误

如何在 swift 中使用 HealthKit 获得步行和跑步距离

带有 Swift 的 HealthKit 中用于 HKBloodGlucose 的 HKUnit