SwiftUI MVVM:如何绑定带关联值枚举的数组至编辑视图
解决方案:带关联值枚举的数组数据绑定完全可行
你的问题核心在于模型属性不可变以及绑定传递方式错误,以下是具体修正步骤:
1. 修正数据模型的可变性
所有需要编辑的属性必须改为var(原代码中大量使用let导致无法修改):
struct SportProgramModel: Codable, Identifiable { var id = UUID().uuidString var title: String // 改为var,支持编辑 var description: String // 改为var let author: String var shared: Bool // 改为var var trainingDays: [TrainingDayListValues] // 改为var } struct TrainingDayListValues: Codable, Identifiable { var id = UUID().uuidString var trainingDayType: trainingDaysTypes // 改为var,支持修改枚举值 } enum trainingDaysTypes: Codable { case workout(TrainingDayModel) case rest(String) } struct TrainingDayModel: Codable, Identifiable { var id = UUID().uuidString var title: String // 改为var,支持编辑标题 var setOfExercises: [SetOfExercisesModel] // 改为var,支持修改训练内容 } // 补充缺失的SetOfExercisesModel定义(假设需要支持编辑) struct SetOfExercisesModel: Codable, Identifiable { var id = UUID().uuidString var name: String // 其他训练属性... }
2. 优化ViewModel结构
移除单独的trainingDaysList,直接操作sportProgram中的数组,确保数据同步:
@MainActor class SportProgramViewModel: ObservableObject{ @Published var sportProgram: SportProgramModel? func getSportProgramDetails(sportProgramID: String) async { do{ let sportProgram = try await SportManager.shared.getSportProgram(id: sportProgramID) self.sportProgram = sportProgram } catch { print("Error when getting sport program") } } // 新增:添加训练日的方法 func addNewTrainingDay() { let newDay = TrainingDayListValues(trainingDayType: .workout(TrainingDayModel(title: "", setOfExercises: []))) sportProgram?.trainingDays.append(newDay) } }
3. 父视图中传递枚举关联值的绑定
通过ForEach的索引绑定,找到对应的TrainingDayListValues元素,再针对.workout关联值创建绑定传递给子视图:
struct SportProgramView: View { @ObservedObject var sportProgramViewModel = SportProgramViewModel() @Environment(\.dismiss) var dismiss // 直接绑定ViewModel中的sportProgram属性,替代单独的State变量 private var sportProgramBinding: Binding<SportProgramModel> { $sportProgramViewModel.sportProgram ?? .constant(SportProgramModel(title: "", description: "", author: "", shared: false, trainingDays: [])) } var sportPorgramID: String? var body: some View { VStack{ VStack { TextField("Name your program", text: sportProgramBinding.title) Divider() TextField("Enter description", text: sportProgramBinding.description) Text("author id: " + sportProgramViewModel.sportProgram?.author ?? "") } List{ // 使用索引绑定来获取可修改的元素 ForEach(sportProgramBinding.trainingDays.indices, id: \.self) { index in let dayBinding = $sportProgramViewModel.sportProgram!.trainingDays[index] switch dayBinding.wrappedValue.trainingDayType { case .workout(let workout): NavigationLink { // 创建TrainingDayModel的绑定 let workoutBinding = Binding<TrainingDayModel>( get: { if case .workout(let model) = dayBinding.wrappedValue.trainingDayType { return model } else { return TrainingDayModel(title: "", setOfExercises: []) } }, set: { newModel in dayBinding.wrappedValue.trainingDayType = .workout(newModel) } ) TrainingDayView(trainingDay: workoutBinding) } label: { Text(workout.title) } case .rest(let rest): Text("Rest for " + rest + " days") } } .onDelete(perform: deleteDay) // 可选:添加删除功能 } HStack { Button { sportProgramViewModel.addNewTrainingDay() } label: { HStack { Image(systemName: "plus.app.fill") Text("Add day") } } } } .onAppear{ if let id = sportPorgramID{ Task{ await sportProgramViewModel.getSportProgramDetails(sportProgramID: id) } } else { // 如果是新建项目,初始化空模型 sportProgramViewModel.sportProgram = SportProgramModel(title: "", description: "", author: "", shared: false, trainingDays: []) } } .toolbar{ Button { Task{ guard let user = UserManager.shared.firebaseAuth.currentUser.userId else { return } if let program = sportProgramViewModel.sportProgram { // TODO: 保存逻辑,直接传入program即可 // await SportManager.shared.saveSportProgram(program) } dismiss() } } label: { Image(systemName: "checkmark.circle.fill") } } .navigationTitle("New program") } private func deleteDay(at offsets: IndexSet) { sportProgramBinding.trainingDays.remove(atOffsets: offsets) } } #Preview { SportProgramView() }
4. 修改子视图接收绑定
将子视图的let trainingDay改为Binding<TrainingDayModel>,实现双向绑定:
struct TrainingDayView: View { @Environment(\.dismiss) var dismiss @Binding var trainingDay: TrainingDayModel // 改为Binding var body: some View { NavigationStack{ VStack{ TextField("Name your day", text: $trainingDay.title) // 这里可以继续添加训练内容的编辑UI,直接绑定trainingDay.setOfExercises } .padding() .navigationTitle("Edit Training Day") .toolbar{ Button("Done") { dismiss() } } } } } #Preview { TrainingDayView(trainingDay: .constant(TrainingDayModel(title: "Leg Day", setOfExercises: []))) }
关键说明
- 枚举的关联值绑定需要手动创建
Binding,通过get和set方法来读写枚举中的关联值 - 所有需要编辑的模型属性必须是可变的(
var),否则无法实现双向绑定 - ViewModel应统一管理数据源,避免拆分数据导致同步问题
内容的提问来源于stack exchange,提问作者Ruzlev
相关产品推荐
相关产品推荐

