I’m having an issue where my data in my charts are being continuously being re-updated. I believe it is with my onAppear modifier when requesting authorization for HealthKit. I tried to use onDisappear and also trying to the query for the variable but it doesn’t seem to be working in my favor. Upon initial launch of the app the Exercise time data is fine. But as I switch views there is the accumulation. My ChartView is using an @ObservedObject of the viewmodel and I am using the reduce method to calculate the average time ( Text(“Exercise Time Average: (healthStoreVM.exerciseTime.reduce(0, { $0 + $1.exerValue / 7})) mins”)). I’m sorry if the code below is too much or too little. I can add more of it if needed. I’ve posted a video here to display the issue.
struct MainScreenView: View {
@StateObject var healthStore = HealthStoreViewModel()
var body: some View {
TabView(selection: $healthStore.selectedTab) {
QuickView(healthStoreVM: healthStore)
.tabItem {
Label("Today", systemImage: "list.bullet.clipboard")
}
.tag(1)
StatsView(healthStoreVM: healthStore)
.tabItem {
Label("Statistics", systemImage: "chart.xyaxis.line")
}
.tag(2)
SettingsView(
stepGoal: $healthStore.stepGoal,
exerciseDayGoal: $healthStore.exerciseDayGoal, exerciseWeeklyGoal: $healthStore.exerciseWeeklyGoal,
healthStoreVM: healthStore)
.tabItem {
Label("Settings", systemImage: "slider.horizontal.3")
}
.tag(3)
}
}
}
struct QuickView: View {
@ObservedObject var healthStoreVM: HealthStoreViewModel
@State private var showInfoSheet = false
var body: some View {
NavigationStack {
GeometryReader { geo in
ScrollView {
VStack(spacing: 5) {
HStack(spacing: 50) {
ExerciseGaugeView(progress: Double(healthStoreVM.currentExTime), minValue: 0.0, maxValue: Double(healthStoreVM.exerciseDayGoal), title: "Today")
ExerciseGaugeView(progress: Double(healthStoreVM.currentExTime), minValue: 0.0, maxValue: Double(healthStoreVM.exerciseWeeklyGoal), title: "Weekly")
}
.padding(.top, 20)
VStack (spacing: 5) {
StepCountCardView(
progress: Double(healthStoreVM.currentStepCount),
minValue: 0.0,
maxValue: Double(healthStoreVM.stepGoal),
title: "\(healthStoreVM.stepCountPercent)%",
goalText: healthStoreVM.stepGoal)
CurrentSummaryCardView(
title: "Resting HR",
imageText: "heart.fill",
description: healthStoreVM.restHRDescription, color: .red,
categoryValue: "\(healthStoreVM.currentRestHR)")
CurrentSummaryCardView(
title: "Energy Burned",
imageText: "flame.fill",
description: "",
color: .orange,
categoryValue: "\(healthStoreVM.currentKcalsBurned)")
}
.padding(.top, 30)
}
.onAppear {
healthStoreVM.requestUserAuthorization()
}
.frame(minWidth: geo.size.width * 0.8, maxWidth: .infinity)
.padding(.horizontal)
.navigationTitle("Health Project App")
}
}
}
}
}
I’ll try to limit only the exercise variable from my ViewModel
class HealthStoreViewModel: ObservableObject {
var healthStore: HKHealthStore?
var exerciseTimeQuery: HKStatisticsCollectionQuery?
@Published var selectedTab = 1
@Published var exerciseTime: [ExerciseTime] = [ExerciseTime]()
@Published var exerciseTimeMonth: [ExerciseTime] = [ExerciseTime]()
@Published var exerciseTime3Months: [ExerciseTime] = [ExerciseTime]()
var currentExTime: Int {
exerciseTime.last?.exerValue ?? 0
}
init(){
if HKHealthStore.isHealthDataAvailable(){
healthStore = HKHealthStore()
} else {
print("HealthKit is unavailable on this platform")
}
}
func requestUserAuthorization() {
let exerciseTimeType = HKQuantityType.quantityType(forIdentifier: .appleExerciseTime)!
let healthTypes = Set([stepType, restingHeartRateType, exerciseTimeType, caloriesBurnedType])
guard let healthStore = self.healthStore else { return }
healthStore.requestAuthorization(toShare: [], read: healthTypes) { success, error in
if success {
self.calculateStepCountData()
self.calculateRestingHRData()
self.calculateSevenDaysExerciseTime()
self.calculateMonthExerciseTime()
self.calculate3MonthExerciseTime()
self.calculateCaloriesBurned()
}
}
}
func calculateSevenDaysExerciseTime() {
let exerciseTimeType = HKQuantityType.quantityType(forIdentifier: .appleExerciseTime)!
let anchorDate = Date.mondayAt12AM()
let daily = DateComponents(day: 1)
let oneWeekAgo = Calendar.current.date(byAdding: DateComponents(day: -7), to: Date())!
let startDate = Calendar.current.date(byAdding: DateComponents(day: -6), to: Date())!
let predicate = HKQuery.predicateForSamples(withStart: oneWeekAgo, end: nil, options: .strictStartDate)
exerciseTimeQuery = HKStatisticsCollectionQuery(quantityType: exerciseTimeType,
quantitySamplePredicate: predicate,
options: .cumulativeSum,
anchorDate: anchorDate,
intervalComponents: daily)
exerciseTimeQuery!.initialResultsHandler = {
exerciseTimeQuery, statisticsCollection, error in
guard let statisticsCollection = statisticsCollection else { return}
statisticsCollection.enumerateStatistics(from: startDate, to: Date()) { statistics, stop in
if let exerciseTimequantity = statistics.sumQuantity() {
let exerciseTimedate = statistics.startDate
//Exercise Time
let exerciseTimevalue = exerciseTimequantity.doubleValue(for: .minute())
let exTime = ExerciseTime(exerValue: Int(exerciseTimevalue), date: exerciseTimedate)
DispatchQueue.main.async {
self.exerciseTime.append(exTime)
}
}
}
}
exerciseTimeQuery!.statisticsUpdateHandler = {
exerciseTimeQuery, statistics, statisticsCollection, error in
guard let statisticsCollection = statisticsCollection else { return }
statisticsCollection.enumerateStatistics(from: startDate, to: Date()) { statistics, stop in
if let exerciseTimequantity = statistics.sumQuantity() {
let exerciseTimedate = statistics.startDate
let exerciseTimevalue = exerciseTimequantity.doubleValue(for: .minute())
let exTime = ExerciseTime(exerValue: Int(exerciseTimevalue), date: exerciseTimedate)
DispatchQueue.main.async {
self.exerciseTime.append(exTime)
}
}
}
}
guard let exerciseTimeQuery = self.exerciseTimeQuery else { return }
self.healthStore?.execute(exerciseTimeQuery)
}