SwiftUI中ViewModel与View间数据传递失败问题求助
问题排查与修复
核心问题分析
- 线程错误:Firebase的
getData()在后台Task执行,更新@Published属性时未切换回主线程,SwiftUI无法感知属性变化。 - 状态对象声明错误:View中用
@ObservedObject初始化ViewModel,View重建时会重新创建实例导致数据丢失,应使用@StateObject(iOS 14+)持有唯一实例。 - 颜色参数传递错误:直接将字符串传入
Color()初始化无效,需先将字符串映射为对应Color值。
修复步骤与代码调整
1. 修复ViewModel的线程问题
在Task中更新yearOfMoods时切换到主线程:
class MoodViewModel: ObservableObject { @Published var yearOfMoods: MoodObject = MoodObject(year: "", jan01: "Reset", jan02: "", jan03: "", jan04: "", jan05: "", jan06: "", jan07: "", jan08: "", jan09: "", jan10: "", jan11: "", jan12: "", jan13: "", jan14: "", jan15: "", jan16: "", jan17: "", jan18: "", jan19: "", jan20: "", jan21: "", jan22: "", jan23: "", jan24: "", jan25: "", jan26: "", jan27: "", jan28: "", jan29: "", jan30: "", jan31: "") func loadMoods(year: String) { guard let user = Auth.auth().currentUser?.uid else { // 处理用户未登录场景 return } let ref = Database.database().reference() let db = ref.child("Users").child(user).child(year).child("Moods") Task { do { let snapshot = try await db.getData() let jan01 = snapshot.childSnapshot(forPath: "jan01").value as? String ?? "Reset" let jan02 = snapshot.childSnapshot(forPath: "jan02").value as? String ?? "Reset" // 其余日期取值代码保持不变 // 切换主线程更新@Published属性 await MainActor.run { self.yearOfMoods = MoodObject(year: year, jan01: jan01, jan02: jan02, jan03: jan03, jan04: jan04, jan05: jan05, jan06: jan06, jan07: jan07, jan08: jan08, jan09: jan09, jan10: jan10, jan11: jan11, jan12: jan12, jan13: jan13, jan14: jan14, jan15: jan15, jan16: jan16, jan17: jan17, jan18: jan18, jan19: jan19, jan20: jan20, jan21: jan21, jan22: jan22, jan23: jan23, jan24: jan24, jan25: jan25, jan26: jan26, jan27: jan27, jan28: jan28, jan29: jan29, jan30: jan30, jan31: jan31) } } catch { print("数据加载失败: \(error.localizedDescription)") } } } }
2. 修复View的ViewModel声明与颜色传递
将@ObservedObject改为@StateObject,新增字符串转Color的辅助逻辑:
struct Mood: View { var year: String = "" // 使用@StateObject确保ViewModel实例唯一 @StateObject var viewModel = MoodViewModel() // 字符串转Color的映射逻辑 private func moodColor(from string: String) -> Color { switch string { case "Red": return .red case "Blue": return .blue case "Green": return .green // 根据需求扩展更多颜色映射,默认用灰色表示"Reset" default: return .gray } } var body: some View { ScrollView{ ScrollView(.horizontal){ HStack{ VStack{ // 日期数字部分代码保持不变 Text(" ") .frame(width: 30, height: 30) Text("1") .frame(width: 30, height: 30) Text("2") .frame(width: 30, height: 30) // ... 其余日期数字代码 } VStack{ Text("J") .frame(width: 30, height: 30, alignment: .center) // 调用辅助函数传递正确Color DayIcon(moodColor: moodColor(from: viewModel.yearOfMoods.jan01), dayOfMonth: "jan01", year: year) DayIcon(moodColor: moodColor(from: viewModel.yearOfMoods.jan02), dayOfMonth: "jan02", year: year) } } } } .padding(15) .task { viewModel.loadMoods(year: year) } } }
3. 额外优化建议:简化MoodObject结构
当前用大量单独的janXX属性,可改为字典减少重复代码:
struct MoodObject { let year: String var dailyMoods: [String: String] // key为"jan01"等,value为颜色字符串 // 兼容原有初始化逻辑 init(year: String, jan01: String = "Reset", jan02: String = "", jan03: String = "", jan04: String = "", jan05: String = "", jan06: String = "", jan07: String = "", jan08: String = "", jan09: String = "", jan10: String = "", jan11: String = "", jan12: String = "", jan13: String = "", jan14: String = "", jan15: String = "", jan16: String = "", jan17: String = "", jan18: String = "", jan19: String = "", jan20: String = "", jan21: String = "", jan22: String = "", jan23: String = "", jan24: String = "", jan25: String = "", jan26: String = "", jan27: String = "", jan28: String = "", jan29: String = "", jan30: String = "", jan31: String = "") { self.year = year self.dailyMoods = [ "jan01": jan01, "jan02": jan02, "jan03": jan03, // ... 其余日期映射 ] } }
内容的提问来源于stack exchange,提问作者Rachel Wielgopolski
相关产品推荐
相关产品推荐

