使用HealthKit时SwiftUI图表数据重复重绘问题求助
问题:HealthKit静息心率数据读取导致图表重绘异常与数据重复
问题描述
从HealthKit读取静息心率数据时,使用带更新处理器的长期查询在后台新增数据,但每次关闭再打开应用,图表会异常重绘,下方列表也产生重复数据。尝试用.onDisappear修饰符停止查询,但仅在强制重启应用后才生效。疑问:这是否是SwiftUI Chart的问题?
相关代码
ViewModel部分
//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) }
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) } }
异常截图

问题分析与解决方案
这不是SwiftUI Chart的问题,核心原因是数据处理逻辑和查询生命周期管理不当:
核心问题点
- 数据重复添加:每次调用
calculateRestingHRData时,没有先清空restingHR数组,旧数据保留的同时新数据又被追加;且statisticsUpdateHandler里重新枚举了全部统计数据,导致所有数据被重复添加一遍。 - 查询未正确停止:
.onDisappear未生效,大概率是因为没有正确调用healthStore.stop(_:)终止查询,且ViewModel作为EnvironmentObject可能未被销毁,查询持续运行并向数组中添加数据。
修复方案
初始化查询前清空数据
在calculateRestingHRData方法开头添加:DispatchQueue.main.async { self.restingHR.removeAll() }避免旧数据残留导致重复。
优化更新处理器逻辑
statisticsUpdateHandler中的statistics参数就是新增的单条统计数据,无需遍历整个collection,直接处理这条数据即可:restingHRquery!.statisticsUpdateHandler = { restingQuery, statistics, statisticsCollection, error in // 错误处理保留 if let error = error as? HKError { switch error.code { case .errorHealthDataUnavailable, .errorNoData: return default: return } } // 仅处理新增的单条统计数据 guard let statistics = statistics, let restHRquantity = statistics.averageQuantity() else { return } let hrdate = statistics.startDate let hrUnit = HKUnit(from: "count/min") let restHRvalue = restHRquantity.doubleValue(for: hrUnit) let restHR = RestingHeartRate(restingValue: Int(restHRvalue), date: hrdate) DispatchQueue.main.async { // 可选:先检查是否已存在该日期的数据,避免重复 if !self.restingHR.contains(where: { $0.date == hrdate }) { self.restingHR.append(restHR) } } }正确实现查询停止逻辑
在ViewModel中添加停止查询的方法:func stopRestingHRQuery() { if let query = restingHRquery { healthStore?.stop(query) restingHRquery = nil } }然后在View的
.onDisappear中调用:struct OneWeekRestHRChartView: View { @EnvironmentObject var healthStoreVM: HealthStoreViewModel var body: some View { // ... 原有View代码 ... } .onDisappear { healthStoreVM.stopRestingHRQuery() } }确保数据唯一性
给RestingHeartRate实现Identifiable协议,或者用日期作为唯一标识去重,避免同一日期的数据被多次添加。
内容的提问来源于stack exchange,提问作者Kevin
相关产品推荐
相关产品推荐

