SwiftUI中步数数据更新时如何自动刷新视图?
问题描述
我开发了一款在屏幕上显示步数的App,但显示步数的视图初始化后,除非重启App,否则步数不会更新。尝试用@ObservableObject和@Published没达到预期效果。
相关代码如下:
HealthStore类代码
class HealthStore: ObservableObject { @Published var healthStore: HKHealthStore? @Published var query: HKStatisticsCollectionQuery? init() { if HKHealthStore.isHealthDataAvailable() { healthStore = HKHealthStore() } } func calculateSteps(completion: @escaping (HKStatisticsCollection?)-> Void) { let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.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] ) query = HKStatisticsCollectionQuery( quantityType: stepType, quantitySamplePredicate: compoundPredicate, options: .cumulativeSum, anchorDate: anchorDate, intervalComponents: daily) query!.initialResultsHandler = { query, statisticsCollection, error in completion(statisticsCollection) } if let healthStore = healthStore, let query = self.query { healthStore.execute(query) } } func requestAuthorization(completion: @escaping (Bool) -> Void) { let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)! guard let healthStore = self.healthStore else { return completion(false) } healthStore.requestAuthorization(toShare: [], read: [stepType]) { (success, error) in completion(success) } } }
视图加载逻辑(onLoad)
.onLoad { if let healthStore = healthStore { healthStore.requestAuthorization { success in if success { healthStore.calculateSteps { statisticsCollection in if let statisticsCollection = statisticsCollection { // update the UI updateUIFromStatistics(statisticsCollection) } } } } } }
onLoad实现代码
struct ViewDidLoadModifier: ViewModifier { @State private var didLoad = false private let action: (() -> Void)? init(perform action: (() -> Void)? = nil) { self.action = action } func body(content: Content) -> some View { content.onAppear { if didLoad == false { didLoad = true action?() } } } } extension View { func onLoad(perform action: (() -> Void)? = nil) -> some View { modifier(ViewDidLoadModifier(perform: action)) } }
解决方案
要实现步数变化时自动刷新视图,需要从数据存储、实时监听、视图绑定三个方面修改:
1. 在HealthStore中添加可发布的步数数据属性
原来的HealthStore只发布了healthStore和query,没有存储实际的步数统计结果,视图无法感知数据变化。添加一个@Published属性来保存步数统计集合:
class HealthStore: ObservableObject { // 新增:存储步数统计数据,发布给视图观察 @Published var dailyStepsCollection: HKStatisticsCollection? @Published var healthStore: HKHealthStore? @Published var query: HKStatisticsCollectionQuery? // ... 其余原有代码不变 }
2. 修改查询逻辑,添加实时更新回调
原来的HKStatisticsCollectionQuery只设置了initialResultsHandler,只会返回一次初始数据。需要添加updateHandler来监听后续的步数变化,并且在回调中更新发布的属性(注意要切换到主线程,因为UI更新必须在主线程执行):
func calculateSteps() { let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.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] ) query = HKStatisticsCollectionQuery( quantityType: stepType, quantitySamplePredicate: compoundPredicate, options: .cumulativeSum, anchorDate: anchorDate, intervalComponents: daily) // 初始结果回调:更新步数数据 query!.initialResultsHandler = { [weak self] query, statisticsCollection, error in DispatchQueue.main.async { self?.dailyStepsCollection = statisticsCollection } } // 新增实时更新回调:当有新步数数据时自动更新 query!.updateHandler = { [weak self] query, statisticsCollection, error in DispatchQueue.main.async { self?.dailyStepsCollection = statisticsCollection } } if let healthStore = healthStore, let query = self.query { healthStore.execute(query) } }
注意这里去掉了completion回调,直接通过@Published属性通知视图更新。
3. 视图直接绑定HealthStore的步数数据
视图中不需要手动维护UI数据,直接观察HealthStore的dailyStepsCollection,当它变化时自动刷新UI。修改视图代码:
// 假设你的视图是这样的结构 struct StepCountView: View { // iOS14+建议用@StateObject,避免视图重建重复创建实例 @StateObject var healthStore = HealthStore() var body: some View { VStack { if let collection = healthStore.dailyStepsCollection { Text("今日步数: \(getTodaySteps(from: collection))") .font(.title) } else { Text("正在加载步数...") } } .onLoad { healthStore.requestAuthorization { success in if success { healthStore.calculateSteps() } } } } // 辅助函数:从统计集合中获取今日步数 private func getTodaySteps(from collection: HKStatisticsCollection) -> Int { let today = Date() var steps = 0 collection.enumerateStatistics(from: today, to: today) { statistics, stop in if let sum = statistics.sumQuantity() { steps = Int(sum.doubleValue(for: HKUnit.count())) } } return steps } }
这样一来,当dailyStepsCollection更新时,视图会自动刷新,显示最新的步数。
4. 额外注意事项
- 确保
HealthStore实例在视图中用@ObservedObject(iOS13+)或@StateObject(iOS14+)修饰,这样才能正确观察属性变化。 - 如果需要监听更实时的步数变化(比如每走一步就更新),可以改用
HKObserverQuery配合HKStatisticsQuery,但日常场景下HKStatisticsCollectionQuery的updateHandler已经足够满足需求。
内容的提问来源于stack exchange,提问作者Mxyb
相关产品推荐
相关产品推荐

