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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 16:00:49