You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 07:15:26