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
相关产品推荐
相关产品推荐

