简体   繁体   中英

Why is m view not updating using ObservedObject and HealthKit?

In the Home Screen of my app, I have a Capsule() that contains the user's steps. This data is obtained via HealthKit. The data is correct however when it changes in the health app, it should change in my app but this is not happening. How do I get 'steps' variable to listen to HealthKit, there must be an error in my code.

Here is the code for my Home view:

import SwiftUI
import HealthKit

struct Home: View {
        
    @ObservedObject var healthStore = HealthStore()
    @State private var steps: [Step] = [Step]()

    init() {
        healthStore = HealthStore()
    }
    
    private func updateUIFromStatistics(_ statisticsCollection: HKStatisticsCollection) {
        
        let startDate = Date()
        let endDate = Date()
        
        statisticsCollection.enumerateStatistics(from: startDate, to: endDate) { (statistics, stop) in
            
            let count = statistics.sumQuantity()?.doubleValue(for: .count())
            let step = Step(count: Int(count ?? 0), date: statistics.startDate)
            steps.append(step)
        }
    }
    
    var body: some View {
        
        NavigationView {
        
        ScrollView {
            
            ZStack {
                Color("BackgroundColour")
                    .ignoresSafeArea()
                
                    VStack {
                        let totalSteps = steps.reduce(0) { $0 + $1.count }
                        ForEach($steps, id: \.id) { step in
                            Button(action: {
                                // Perform button action here
                                print("Step Capsule Tapped...")
                            }) {
                                HStack {
                                    Image("footsteps")

                                    Text("\(totalSteps)")
                                }
                            }
                        } // ForEach End
                    } // VStack End
            }//ZStack End
            .edgesIgnoringSafeArea(.all)
        } // ScrollView End
        .background(Color("BackgroundColour"))
        .onLoad {
            healthStore.requestAuthorization { success in
                if success {
                    healthStore.calculateSteps { statisticsCollection in
                        if let statisticsCollection = statisticsCollection {
                            // update the UI
                            updateUIFromStatistics(statisticsCollection)
                        }
                    }
                }
            }
        } // .onLoad End
        .onAppear(perform: {
            let defaults = UserDefaults.standard
            let keyString: String? = defaults.string(forKey: "key") ?? ""
            print("User's Key:\(keyString ?? "")")
        }) // .onAppear End
        } // NavigationView End
    }
}

Here is the code for the HealthStore:

import Foundation
import HealthKit
import SwiftUI
import Combine


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

    func calculateSteps(completion: @escaping (HKStatisticsCollection?)-> Void) {
        
        let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!
        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)
        let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates:
                                    [.init(format: "metadata.%K != YES", HKMetadataKeyWasUserEntered), predicate]
                                )
        query = HKStatisticsCollectionQuery(
           quantityType: stepType,
           quantitySamplePredicate: compoundPredicate,
           options: .cumulativeSum,
           anchorDate: anchorDate,
           intervalComponents: daily)

        query!.initialResultsHandler = { query, statisticsCollection, error in
            completion(statisticsCollection)
            
        }
        if let healthStore = healthStore, let query = self.query {
            healthStore.execute(query)
        }
    }
    func requestAuthorization(completion: @escaping (Bool) -> Void) {
        
        let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!
        
        guard let healthStore = self.healthStore else { return completion(false) }
        
        healthStore.requestAuthorization(toShare: [], read: [stepType]) { (success, error) in
            completion(success)
        }
    }
}

extension Date {
    static func mondayAt12AM() -> Date {
        return Calendar(identifier: .iso8601).date(from: Calendar(identifier: .iso8601).dateComponents([.yearForWeekOfYear, .weekOfYear], from: Date()))!
    }
}

Here is the code for Step:

import Foundation

struct Step: Identifiable {
    let id = UUID()
    let count: Int
    let date: Date
}

Here is the code for the.onLoad method that is used on the Home view:

import SwiftUI

struct ViewDidLoadModifier: ViewModifier {

    @State private var didLoad = false
    private let action: (() -> Void)?

    init(perform action: (() -> Void)? = nil) {
        self.action = action
    }

    func body(content: Content) -> some View {
        content.onAppear {
            if didLoad == false {
                didLoad = true
                action?()
            }
        }
    }
}

extension View {

    func onLoad(perform action: (() -> Void)? = nil) -> some View {
        modifier(ViewDidLoadModifier(perform: action))
    }

}

Any ideas?

I think I know what's missing here.

In your class HealthStore , inside the method func calculateSteps(completion: @escaping (HKStatisticsCollection?)-> Void) , you have set the initialResultsHandler property of your HKStatisticsCollectionQuery . However, you've not set the statisticsUpdateHandler property.

To continue to listen to updates from HealthKit, after initialResultsHandler completes execution, you need to also set statisticsUpdateHandler .

As-per the statisticsUpdateHandler documentation , in-case statisticsUpdateHandler is nil, which is the case above, the HKStatisticsCollectionQuery will:

"automatically stop as soon as it has finished calculating the initial results"

which seems to be in-line with what you are observing.

Full disclosure, I've never worked with HealthKit, so please pardon me if this doesn't help. I've gone through the HealthKit documentation after looking at your code and writing here what jumps out at me.

If it does help you though, kindly consider marking it as the answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM