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

SwiftUI集成HealthKit:周步数图表不显示,Dispatch延迟异常求助

SwiftUI HealthKit周步数图表显示问题解决方案

我在SwiftUI开发中需要展示从HealthKit获取的按日拆分的周步数数据(例如周一-1200、周二-800)。目前已编写ViewModel获取数据,但不在View里用Dispatch设置延迟的话,数据无法打印;加了延迟后图表又完全不显示,求解决办法。

核心问题与解决方案要点

  • 让ViewModel数据可被观察:将weeklySteps标记为@Published,数据更新时自动通知View刷新,无需手动延迟。
  • 修正View状态声明:@ObservedObject必须放在结构体属性层级,不能在body内部声明;直接绑定ViewModel的weeklySteps,不需要额外复制到@State变量。
  • 线程切换修正:HealthKit查询回调在后台线程,更新ViewModel数据时必须切回主线程,避免UI更新异常。
  • 修复周日期计算:调整一周起始日期的逻辑,确保查询时间范围准确。

修改后的代码

View代码

import SwiftUI
import Charts

struct DashboardView: View {
    // 将ObservedObject移到结构体属性位置
    @ObservedObject var healthKitManager = HealthKitManager()
    
    var body: some View {
        VStack {
            VStack {
                HStack{
                    Text("Hello")
                        .foregroundColor(Color.black)
                        .font(.largeTitle)
                    Text("Leander 👋")
                        .foregroundColor(Color.black)
                        .font(.largeTitle)
                        .fontDesign(.serif)
                        .fontWeight(.bold)
                }
                .padding(10)
                
                VStack{
                    Text("WANT TO SEE YOUR NEARBY USERS?")
                        .padding(.leading, 70)
                        .font(.subheadline)
                        .foregroundColor(Color("purple"))
                    Divider()
                        .frame(minHeight: 1)
                        .overlay(Color("Green"))
                        .padding(.leading, 70)
                    
                    Text("WANT TO SEE CONNETIONS MADE?")
                        .font(.subheadline)
                        .padding(.leading, 70)
                        .foregroundColor(Color("Green"))
                }
                .padding(.vertical, 3)
                
                VStack {
                    Text("Connections made:")
                    Spacer()
                        .frame(height: 20)
                    // 直接使用ViewModel的weeklySteps
                    Chart {
                        ForEach(healthKitManager.weeklySteps) { item in
                            LineMark(
                                x: .value("date", item.day),
                                y: .value("connections", item.steps)
                            )
                        }
                    }
                    .frame(maxHeight: 150)
                    Spacer()
                    Text("Distance walked:")
                        .padding(.bottom, 5)
                }
                .padding(20)
            }
            .frame(maxWidth: .infinity, maxHeight: 900, alignment: .top)
            .padding(20)
            .background(Color.white)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
        .background(Color("Green"))
        .onAppear{
            // 不再需要延迟,ViewModel数据更新会自动触发View刷新
            print("View appeared, starting data fetch")
        }
    }
}

struct DashboardView_Previews: PreviewProvider {
    static var previews: some View {
        DashboardView()
    }
}

ViewModel代码

import Foundation
import HealthKit

class HealthKitManager: ObservableObject {
    let healthStore: HKHealthStore = HKHealthStore()
    let steps = HKQuantityType(.stepCount)
    
    var thisWeekSteps: [Int: Int] = [1: 0, 2: 0, 3: 0,
                                       4: 0, 5: 0, 6: 0, 7: 0]
    // 用@Published标记,让View能观察到数据变化
    @Published var weeklySteps: [StepsModel] = []
    
    init(){
        if HKHealthStore.isHealthDataAvailable() {
            let heartBeat = HKQuantityType(.heartRate)
            let healthTypes: Set = [steps, heartBeat]
            
            Task{
                do{
                    try await healthStore.requestAuthorization(toShare: [], read: healthTypes)
                    fetchSteps()
                }catch{
                    print("Error retrieving data: \(error)")
                }
            }
        }
    }
    
    func fetchSteps() {
        guard let stepCountType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
            return
        }
        
        let calendar = Calendar.current
        let today = calendar.startOfDay(for: Date())
        // 修正周起始日期计算
        guard let startOfWeek = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: today)) else {
            print("Failed to calculate the start date of the week.")
            return
        }
        guard let endOfWeek = calendar.date(byAdding: .day, value: 6, to: startOfWeek) else {
            print("Failed to calculate the end date of the week.")
            return
        }
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "EEEE"
        
        let predicate = HKQuery.predicateForSamples(
            withStart: startOfWeek,
            end: today,
            options: .strictStartDate
        )
        
        let query = HKStatisticsCollectionQuery(
            quantityType: stepCountType,
            quantitySamplePredicate: predicate,
            options: .cumulativeSum,
            anchorDate: startOfWeek,
            intervalComponents: DateComponents(day: 1)
        )
        
        query.initialResultsHandler = { _, result, error in
            guard let result = result, error == nil else {
                print("Error getting steps \(error?.localizedDescription ?? "Unknown error")")
                return
            }
            
            // 切换到主线程更新数据,避免UI异常
            DispatchQueue.main.async {
                self.weeklySteps.removeAll() // 先清空旧数据
                result.enumerateStatistics(from: startOfWeek, to: today) { statistics, _ in
                    if let quantity = statistics.sumQuantity() {
                        let steps = Int(quantity.doubleValue(for: HKUnit.count()))
                        let date = statistics.startDate
                        let dayOfWeek = dateFormatter.string(from: date)
                        self.weeklySteps.append(StepsModel(day: dayOfWeek, steps: steps))
                        print("\(dayOfWeek) - \(steps)")
                    }
                }
            }
        }
        
        healthStore.execute(query)
    }
    
    func readStepCountThisWeek() {
        guard let stepCountType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
            return
        }
        let calendar = Calendar.current
        let today = calendar.startOfDay(for: Date())
        
        guard let startOfWeek = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: today)) else {
            print("Failed to calculate the start date of the week.")
            return
        }
        
        guard let endOfWeek = calendar.date(byAdding: .day, value: 6, to: startOfWeek) else {
            print("Failed to calculate the end date of the week.")
            return
        }
        
        let predicate = HKQuery.predicateForSamples(
            withStart: startOfWeek,
            end: today,
            options: .strictStartDate
        )
        
        let query = HKStatisticsCollectionQuery(
            quantityType: stepCountType,
            quantitySamplePredicate: predicate,
            options: .cumulativeSum,
            anchorDate: startOfWeek,
            intervalComponents: DateComponents(day: 1)
        )
        
        query.initialResultsHandler = { _, result, error in
            guard let result = result else {
                if let error = error {
                    print("An error occurred while retrieving step count: \(error.localizedDescription)")
                }
                return
            }
            
            DispatchQueue.main.async {
                self.thisWeekSteps = [1:0,2:0,3:0,4:0,5:0,6:0,7:0]
                result.enumerateStatistics(from: startOfWeek, to: endOfWeek) { statistics, _ in
                    if let quantity = statistics.sumQuantity() {
                        let steps = Int(quantity.doubleValue(for: HKUnit.count()))
                        let day = calendar.component(.weekday, from: statistics.startDate)
                        self.thisWeekSteps[day] = steps
                        print("day: \(day) - steps: \(steps)")
                    }
                }
            }
        }
        
        healthStore.execute(query)
    }
}

extension Calendar {
    static var firstWeekday: Date {
        let calendar = Calendar.current
        let now = Date()
        let beginningOfWeek = calendar.dateInterval(of: .weekOfYear, for: now)?.start
        return beginningOfWeek ?? now
    }
}

数据模型代码(无需修改)

import Foundation

struct StepsModel: Identifiable, Codable, Equatable {
    var id = UUID()
    var day: String
    var steps: Int
}

内容的提问来源于stack exchange,提问作者DevAcc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 19:17:32