SwiftData:进入AddWorkoutView时初始化Workout并传递给子视图
解决方案:SwiftData中初始化Workout并关联新增Exercise
核心思路
不要在onAppear中直接将Workout插入modelContext(会导致用户取消创建后数据库残留空Workout),而是先创建草稿Workout实例保存在视图状态中,用户添加Exercise时传递该草稿,最后点击「Save」时再正式持久化到数据库。
步骤1:重构AddWorkoutView
修改视图逻辑,引入草稿Workout,调整查询和sheet传递逻辑:
import SwiftUI import SwiftData struct AddWorkoutView: View { @Environment(\.modelContext) var modelContext @State private var draftWorkout: Workout? // 草稿Workout,未插入数据库 @State private var workoutName: String = "" @State private var showExerciseSheet = false // 只查询当前草稿Workout关联的Exercise private var draftExercises: [Exercise] { draftWorkout?.exercises ?? [] } var body: some View { VStack() { VStack(spacing: 60) { VStack { HStack{ Text("Create New Workout") .font(.title) .fontWeight(.semibold) Spacer() } .padding(.leading) TextField("Enter workout name",text: $workoutName) .textFieldStyle(.roundedBorder) .font(.title3) .padding() } if(draftExercises.isEmpty) { VStack(alignment: .leading ,spacing: 10){ Text("Exercises") .font(.headline) .foregroundStyle(.gray) Text("Press Add Exercise to create your first exercise") .foregroundStyle(.gray) .font(.subheadline) } } else { List(draftExercises) { exercise in Text(exercise.exerciseName.name) } } Button { showExerciseSheet = true } label: { Text("Add Exercise") .font(.headline) .foregroundStyle(.white) .frame(height: 55) .frame(maxWidth: .infinity) .background(Color.blue) .clipShape(RoundedRectangle(cornerRadius: 16)) .shadow(radius: 2) .padding() } Spacer() } .toolbar { Button { // 保存草稿Workout到数据库 guard let workout = draftWorkout else { return } workout.name = workoutName modelContext.insert(workout) // 如需导航回上一页,可在此添加dismiss逻辑 } label: { Text("Save") .font(.headline) .underline() .foregroundStyle(.blue) .fontWeight(.semibold) .padding() } } .sheet(isPresented: $showExerciseSheet) { // 传递草稿Workout给AddExerciseView AddExerciseView(workout: draftWorkout!) .presentationDragIndicator(.visible) } .onAppear { // 初始化草稿Workout,不插入数据库 if draftWorkout == nil { draftWorkout = Workout(id: UUID(), date: Date.now) } } } } }
步骤2:修改AddExerciseView
添加接收Workout的参数,创建Exercise时关联该Workout:
import Foundation import SwiftUI import SwiftData struct AddExerciseView: View { @Environment(\.modelContext) var modelContext @Query var exercisesNames: [ExerciseName] @State private var showNameInput = false @State private var exerciseName = "" @State private var selection: ExerciseName? @Environment(\.dismiss) private var dismiss private let workout: Workout // 接收传递过来的草稿Workout // 初始化方法,接收Workout参数 init(workout: Workout) { self.workout = workout _exercisesNames = Query() // 保持原Query初始化逻辑 } var body: some View { VStack { HStack{ Spacer() Button { showNameInput.toggle() } label: { showNameInput ? Image(systemName: "minus.circle") .resizable() .frame(width: 24, height: 24) : Image(systemName: "plus.circle") .resizable() .frame(width: 24, height: 24) } .padding(28) } if(showNameInput == true) { HStack{ TextField("Enter Exercise Name", text: $exerciseName) .frame(height: 35) .textFieldStyle(.roundedBorder) .font(.title3) .padding(.leading) Button { if (!exerciseName.isEmpty) { let newExeriseName = ExerciseName(name: exerciseName) modelContext.insert(newExeriseName) showNameInput.toggle() } }label: { Text("Add") } .font(.headline) .foregroundStyle(.white) .frame(height: 35) .frame(width: 70) .background(Color.blue) .clipShape(RoundedRectangle(cornerRadius: 8)) .shadow(radius: 1) .padding() } } List(exercisesNames,id: \.self, selection: $selection) { exercisename in Text(exercisename.name) } Button { guard let selectedName = selection else { return } // 创建Exercise并关联Workout let newExercise = Exercise(id: UUID(), exerciseName: selectedName, date: Date.now) newExercise.workout = workout workout.exercises?.append(newExercise) // 维护双向关联 dismiss() } label: { Text("Done") .font(.headline) .foregroundStyle(.white) .frame(height: 55) .frame(maxWidth: .infinity) .background(Color.blue) .clipShape(RoundedRectangle(cornerRadius: 16)) .shadow(radius: 2) .padding() } } .onAppear { if exercisesNames.isEmpty { showNameInput=true } } } } #Preview { // 预览时创建临时Workout let workout = Workout(id: UUID(), date: Date.now) return AddExerciseView(workout: workout) }
关键说明
- 草稿Workout的优势:用户未点击「Save」前,Workout不会进入数据库,避免无效数据残留;
- 双向关联维护:创建Exercise时同时设置
exercise.workout = workout和workout.exercises?.append(exercise),确保SwiftData的关系正确同步; - 查询逻辑修正:原
@Query var exercises: [Exercise]会查询所有Exercise,改为直接从草稿Workout的exercises属性获取,只显示当前创建中的Workout关联的Exercise。
内容的提问来源于stack exchange,提问作者Milan Labus
相关产品推荐
相关产品推荐

