SwiftUI中ViewModel无法更新视图的问题求助
SwiftUI条件视图不更新:修改ViewModel的@Published变量后视图未切换
我在SwiftUI中使用条件视图时遇到问题:修改ViewModel的@Published变量后,视图没有自动切换,必须重新显示才会更新。不确定是否和SwiftData有关,尝试将dailyMacros改为视图中的@State属性包装器,但问题依旧。
视图代码
struct DiaryView: View { @Environment(\.modelContext) private var modelContext @Query var macros: [Macros] @ObservedObject var viewModel = DiaryViewModel() private func initView() { viewModel.fetchMacros(macros, viewModel.currentDate) if viewModel.dailyMacros == nil { modelContext.insert(Macros()) viewModel.fetchMacros(macros, viewModel.currentDate) } } var body: some View { VStack { DailyDateView(date: $viewModel.currentDate) .onChange(of: viewModel.currentDate) { _ , newValue in viewModel.fetchMacros(macros, newValue) } if viewModel.dailyMacros == nil { VStack(alignment: .center) { Spacer() Text("No data") .bold() .font(.title) Spacer() } } else { HStack { ProgressCircleView(number1: viewModel.dailyMacros!.carbs, number2: viewModel.totalCarbs, color: Color.brown, size: 80, title: NSLocalizedString("Carbs", comment: "")) ProgressCircleView(number1: viewModel.dailyMacros!.fat, number2: viewModel.totalFat, color: Color.orange, size: 80, title: NSLocalizedString("Fat", comment: "")) ProgressCircleView(number1: viewModel.dailyMacros!.proteins, number2: viewModel.totalProteins, color: Color.red, size: 80, title: NSLocalizedString("Proteins", comment: "")) } HStack { ProgressView() .progressViewStyle(CustomProgressBar(number1: viewModel.dailyMacros!.calories, number2: viewModel.totalCalories, color: Color.purple, width: 300, title: "Calories")) } .padding() HStack { IncrementButton(number: $viewModel.incrementCarbs, width: 20, color: Color.brown) IncrementButton(number: $viewModel.incrementFat, width: 20, color: Color.orange) IncrementButton(number: $viewModel.incrementProteins, width: 20, color: Color.red) } HStack { CustomButtonView(action: { viewModel.add() }, label: "Appliquer", color: Color.blue, width: 200, height: 50) } } Spacer() } .onAppear { initView() } } }
ViewModel代码
class DiaryViewModel: ObservableObject { private enum Constants { static let quickAddMaxNumber: CGFloat = 999 static let quickAddMinimalNumber: CGFloat = 0 } @Published var incrementCarbs: CGFloat @Published var incrementProteins: CGFloat @Published var incrementFat: CGFloat @Published var dailyMacros: Macros? var totalCarbs: CGFloat var totalFat: CGFloat var totalProteins: CGFloat var totalCalories: CGFloat @Published var currentDate: Date init(incrementCarbs: CGFloat = 0, incrementProteins: CGFloat = 0, incrementFat: CGFloat = 0, dailyMacros: Macros? = nil, currentDate: Date = .now) { self.incrementCarbs = incrementCarbs self.incrementProteins = incrementProteins self.incrementFat = incrementFat self.dailyMacros = dailyMacros self.totalCarbs = (UserDefaults.standard.string(forKey: "carbs")?.toCGFloat())! self.totalFat = (UserDefaults.standard.string(forKey: "fat")?.toCGFloat())! self.totalProteins = (UserDefaults.standard.string(forKey: "proteins")?.toCGFloat())! self.totalCalories = (UserDefaults.standard.string(forKey: "calories")?.toCGFloat())! self.currentDate = currentDate } func add() { guard let macros = dailyMacros else { return } if checkQuickEntrys() { macros.carbs += incrementCarbs macros.fat += incrementFat macros.proteins += incrementProteins let caloriesFromCarbs = incrementCarbs * 4 let caloriesFromFat = incrementFat * 9 let caloriesFromProteins = incrementProteins * 4 macros.calories += caloriesFromCarbs + caloriesFromFat + caloriesFromProteins } resetMacroIncrements() } private func resetMacroIncrements() { incrementCarbs = 0 incrementFat = 0 incrementProteins = 0 } private func checkQuickEntrys() -> Bool { guard let macros = dailyMacros, macros.carbs + incrementCarbs >= Constants.quickAddMinimalNumber, macros.fat + incrementFat >= Constants.quickAddMinimalNumber, macros.proteins + incrementProteins >= Constants.quickAddMinimalNumber else { return false } guard let macros = dailyMacros, macros.carbs < Constants.quickAddMaxNumber, macros.fat < Constants.quickAddMaxNumber, macros.proteins < Constants.quickAddMaxNumber else { return false } return true } func fetchMacros(_ macros: [Macros], _ date: Date) { self.dailyMacros = macros.first(where: { Calendar.current.isDate($0.date, equalTo: date, toGranularity: .day) }) ?? nil } }
问题分析与解决方法
1. 替换@ObservedObject为@StateObject
在视图中直接初始化@ObservedObject会导致视图重建时重新创建ViewModel实例,无法正确保持状态监听。SwiftUI中持有ObservableObject应该用@StateObject,它会在视图生命周期内维持唯一实例:
// 替换原有的@ObservedObject @StateObject var viewModel = DiaryViewModel()
2. 修复新插入Macros的日期缺失问题
插入新Macros时没有设置date属性,导致fetchMacros无法找到对应日期的数据,这是视图一直显示"No data"的核心原因之一:
private func initView() { viewModel.fetchMacros(macros, viewModel.currentDate) if viewModel.dailyMacros == nil { let newMacros = Macros() newMacros.date = viewModel.currentDate // 新增:设置当前日期 modelContext.insert(newMacros) try? modelContext.save() // 立即保存到SwiftData } }
3. 监听@Query数据更新,自动刷新dailyMacros
@Query的macros数组会在SwiftData数据变化时自动更新,添加监听来同步ViewModel的dailyMacros:
var body: some View { VStack { // ...原有视图代码 } .onAppear { initView() } .onChange(of: macros) { newValue in // 新增:监听macros变化 viewModel.fetchMacros(newValue, viewModel.currentDate) } }
4. 手动触发ViewModel更新(修改Macros内部属性时)
当修改dailyMacros指向的Macros对象内部属性时,由于对象引用未变,@Published不会自动发送更新通知。需要在修改前手动触发ViewModel的更新:
func add() { guard let macros = dailyMacros else { return } if checkQuickEntrys() { objectWillChange.send() // 新增:手动触发视图更新 macros.carbs += incrementCarbs macros.fat += incrementFat macros.proteins += incrementProteins let caloriesFromCarbs = incrementCarbs * 4 let caloriesFromFat = incrementFat * 9 let caloriesFromProteins = incrementProteins * 4 macros.calories += caloriesFromCarbs + caloriesFromFat + caloriesFromProteins try? modelContext.save() // 新增:保存修改到SwiftData } resetMacroIncrements() }
5. 让ViewModel持有ModelContext(可选优化)
为了在ViewModel中直接操作SwiftData,可以在初始化时传入ModelContext:
// ViewModel修改 class DiaryViewModel: ObservableObject { private let modelContext: ModelContext // 新增 // 修改init方法 init(modelContext: ModelContext, incrementCarbs: CGFloat = 0, incrementProteins: CGFloat = 0, incrementFat: CGFloat = 0, dailyMacros: Macros? = nil, currentDate: Date = .now) { self.modelContext = modelContext // ...原有初始化代码 } // add方法中直接使用modelContext func add() { guard let macros = dailyMacros else { return } if checkQuickEntrys() { objectWillChange.send() macros.carbs += incrementCarbs macros.fat += incrementFat macros.proteins += incrementProteins let caloriesFromCarbs = incrementCarbs * 4 let caloriesFromFat = incrementFat * 9 let caloriesFromProteins = incrementProteins * 4 macros.calories += caloriesFromCarbs + caloriesFromFat + caloriesFromProteins try? modelContext.save() } resetMacroIncrements() } } // 视图中初始化ViewModel struct DiaryView: View { @Environment(\.modelContext) private var modelContext @Query var macros: [Macros] @StateObject var viewModel: DiaryViewModel // 修改为非直接初始化 // 新增init方法 init(modelContext: ModelContext) { self.modelContext = modelContext _macros = Query() _viewModel = StateObject(wrappedValue: DiaryViewModel(modelContext: modelContext)) } // ...原有视图代码 }
内容的提问来源于stack exchange,提问作者Killianoni
相关产品推荐
相关产品推荐

