You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
}

关键说明

  1. 草稿Workout的优势:用户未点击「Save」前,Workout不会进入数据库,避免无效数据残留;
  2. 双向关联维护:创建Exercise时同时设置exercise.workout = workout和workout.exercises?.append(exercise),确保SwiftData的关系正确同步;
  3. 查询逻辑修正:原@Query var exercises: [Exercise]会查询所有Exercise,改为直接从草稿Workout的exercises属性获取,只显示当前创建中的Workout关联的Exercise。

内容的提问来源于stack exchange,提问作者Milan Labus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 13:47:06