SwiftUI中Home视图HealthStore数据消失及@StateObject初始化报错解决
解决SwiftUI中Home视图重初始化步数数据消失的问题
问题根源
将@ObservedObject var healthStore = HealthStore()改为@StateObject var healthStore = HealthStore()后,视图init方法中直接赋值healthStore = HealthStore()会报错,因为@StateObject修饰的属性是只读的,由SwiftUI负责实例化与生命周期管理,开发者无法在自定义init中手动修改它。
同时,原@ObservedObject方案下,每次Home视图重建(如重复点击标签)都会创建新的HealthStore实例,导致之前的步数数据丢失,这正是数据消失的核心原因。
解决方案
1. 移除视图init中的手动赋值
删除Home视图内的这段代码:
init() { healthStore = HealthStore() }
直接通过@StateObject声明并初始化实例,让SwiftUI接管其生命周期:
struct HomeView: View { // SwiftUI会在视图整个生命周期内持有该实例,视图重建时不会销毁 @StateObject private var healthStore = HealthStore() // 其他视图代码... }
2. 优化HealthStore的数据更新逻辑(可选)
你的HealthStore类已通过@Published实现数据监听,可补充优化避免重复数据:
func calculateSteps(completion: @escaping (HKStatisticsCollection?) -> Void) { // 先取消已存在的查询,避免重复请求 if let query = self.query, let healthStore = self.healthStore { healthStore.stop(query) } let stepType = HKQuantityType.quantityType(forIdentifier: .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] ) DispatchQueue.main.async { self.query = HKStatisticsCollectionQuery( quantityType: stepType, quantitySamplePredicate: compoundPredicate, options: .cumulativeSum, anchorDate: anchorDate, intervalComponents: daily) self.query!.initialResultsHandler = { [weak self] query, statisticsCollection, error in guard let self = self else { return } DispatchQueue.main.async { // 初始化时清空旧数据,避免重复 self.steps.removeAll() completion(statisticsCollection) } } self.query!.statisticsUpdateHandler = { [weak self] query, statistics, collection, error in guard let self = self else { return } DispatchQueue.main.async { if let stepCount = statistics?.sumQuantity()?.doubleValue(for: HKUnit.count()) { // 根据日期去重更新,而非直接追加 if let index = self.steps.firstIndex(where: { $0.date == statistics!.startDate }) { self.steps[index] = Step(count: Int(stepCount), date: statistics!.startDate) } else { self.steps.append(Step(count: Int(stepCount), date: statistics!.startDate)) } } } } if let healthStore = self.healthStore, let query = self.query { healthStore.execute(query) } } }
3. 视图中正确触发数据加载
在Home视图的onAppear中请求权限并加载数据,确保视图每次出现时都能同步最新数据,同时因@StateObject持有同一个实例,历史数据不会丢失:
struct HomeView: View { @StateObject private var healthStore = HealthStore() var body: some View { List(healthStore.steps) { step in Text("\(step.count)步 - \(step.date.formatted())") } .onAppear { healthStore.requestAuthorization { success in if success { healthStore.calculateSteps { _ in // 处理初始化结果 } } } } } }
生效原理
@StateObject让SwiftUI在视图的整个生命周期内持有同一个HealthStore实例,即使视图重建,实例也不会被销毁,步数数据得以保留- 移除
init中的手动赋值,符合@StateObject的使用规则,规避只读属性赋值错误 - 优化查询逻辑,确保数据更新时不会重复添加,保持数据一致性
内容的提问来源于stack exchange,提问作者Mxyb
相关产品推荐
相关产品推荐

