I can't seem to wrap my head around with long running queries in HealthKit when it comes dealing with the update handlers. I ask because from my end when I upload my code to the physical device, my data is reloaded and appending to my array which is adding additional data to the screen. From my understanding the updateHandlers monitors for new data/values in the background to update the value in a new view. I'm trying to mimic the Apple Fitness/Health app where the value updates and displays it to the user without having to close the app or switch views. I tried to be concise with my code mainly to get an idea of the updateHandler. I tried stopping the query and .onDisappear modifier but the .onDisappear wouldn't update the values unless I force closed the app and opened it again.
//Properties within my view model for resting HR.
var restingHRquery: HKStatisticsCollectionQuery?
@Published var restingHR: [RestingHeartRate] = [RestingHeartRate]()
func calculateRestingHRData() {
let restingHRpredicate = HKQuery.predicateForSamples(withStart: oneWeekAgo, end: nil, options: .strictStartDate)
restingHRquery = HKStatisticsCollectionQuery(quantityType: restingHeartRateType,
quantitySamplePredicate: restingHRpredicate,
options: .discreteAverage,
anchorDate: anchorDate,
intervalComponents: daily)
restingHRquery!.initialResultsHandler = {
restingQuery, statisticsCollection, error in
//Handle errors here
if let error = error as? HKError {
switch (error.code) {
case .errorHealthDataUnavailable:
return
case .errorNoData:
return
default:
return
}
}
guard let statisticsCollection = statisticsCollection else { return}
//Calculating resting HR
statisticsCollection.enumerateStatistics(from: self.startDate, to: self.date) { statistics, stop in
if let restHRquantity = statistics.averageQuantity() {
let hrdate = statistics.startDate
//HR Units
let hrUnit = HKUnit(from: "count/min")
let restHRvalue = restHRquantity.doubleValue(for: hrUnit)
let restHR = RestingHeartRate(restingValue: Int(restHRvalue), date: hrdate)
DispatchQueue.main.async {
self.restingHR.append(restHR)
}
}
}
}
restingHRquery!.statisticsUpdateHandler = {
restingQuery, statistics, statisticsCollection, error in
//Handle errors here
if let error = error as? HKError {
switch (error.code) {
case .errorHealthDataUnavailable:
return
case .errorNoData:
return
default:
return
}
}
guard let statisticsCollection = statisticsCollection else { return}
//Calculating resting HR
statisticsCollection.enumerateStatistics(from: self.startDate, to: self.date) { statistics, stop in
if let restHRquantity = statistics.averageQuantity() {
let hrdate = statistics.startDate
//HR Units
let hrUnit = HKUnit(from: "count/min")
let restHRvalue = restHRquantity.doubleValue(for: hrUnit)
let restHR = RestingHeartRate(restingValue: Int(restHRvalue), date: hrdate)
DispatchQueue.main.async {
self.restingHR.append(restHR)
}
}
}
}
guard let restingHRquery = self.restingHRquery else { return }
self.healthStore?.execute(restingHRquery)
}
My chart always being updated which causes it to reload my list and draw new data on the charts which makes the line go crazy. I'm using the EnvironmentObject because it's a subview. I'm just getting straight to the chart view
struct OneWeekRestHRChartView: View {
@EnvironmentObject var healthStoreVM: HealthStoreViewModel
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Average: \(healthStoreVM.averageRestHR) bpm")
.font(.headline)
Chart {
ForEach(healthStoreVM.restingHR.reversed(), id: \.date) {
restHrData in
LineMark(x: .value("day", restHrData.date, unit: .day),
y: .value("RHR", restHrData.restingValue)
)
.interpolationMethod(.catmullRom)
.foregroundStyle(.red)
.symbol() {
Circle()
.fill(.red)
.frame(width: 15)
}
}
}
.frame(height: 200)
.chartYScale(domain: 30...80)
.chartXAxis {
AxisMarks(values: .stride(by: .day)) {
AxisGridLine()
AxisValueLabel(format: .dateTime.day().month(), centered: true)
}
}
}
.padding(.horizontal)
.navigationTitle("Resting Heart Rate")
List{
ForEach(healthStoreVM.restingHR.reversed(), id: \.date) {
restHR in
DataListView(imageText: "heart.fill",
imageColor: .red,
valueText: "\(restHR.restingValue) bpm",
date: restHR.date)
}
}
.listStyle(.inset)
}
}
Could this just be a SwiftUI chart issue?