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

SwiftUI中检测Workout的Exercise数组变化并更新NavigationPath

问题描述

我在AddWorkoutView中用@State存储SwiftData模型Workout:

@State private var workout: Workout = Workout(id: UUID())

该模型包含Exercise模型的数组。我通过弹窗向Workout添加Exercise后关闭弹窗:

let newExercise = Exercise(id: UUID(),exerciseName: selection!, date: Date.now)

modelContext.insert(newExercise)

// 解包workout并写入
if let workout = workout {
   workout.exercises?.append(newExercise)
}

dismiss()

当前导航栈实现如下,需求是添加完Exercise后直接打开EditExerciseView,无需用户手动点击:

NavigationStack(path: $router.path) {
    VStack {
        List {
            Section(header: Text("Workout Details")) {
                TextField("Enter workout name",text: $workoutName)
                DatePicker("Start Time", selection: $workoutStartTime)
                DatePicker("End Time", selection: $workoutEndTime)
                
            }
            
            Section(header: Text("Exercises")) {
                if let exercises =  workout.exercises {
                    ForEach(exercises){ exercise in
                        NavigationLink(value: exercise) {
                            Text(exercise.exerciseName.name)
                                .padding(.top, 5)
                                .padding(.bottom, 5)
                        }
                    }
                    .onDelete(perform: delete)
                } else {
                    Text("No Exericses Yet")
                }
            }
            
            Section {
                Button {
                    showExercieSheet = true
                    
                } label: {
                    Text("Add Exercise")
                    
                }
            }
            
        }
        .navigationDestination(for: Exercise.self) { exercise in
            EditExeriseView(exercise: exercise, sets: exercise.sets ?? [])
                .navigationBarBackButtonHidden(true)
        }
        .listStyle(.insetGrouped)
        
    }
}

.toolbar {
    ToolbarItem(placement: .topBarLeading) {
        Button{
            showCancelAlert.toggle()
        }label: {
            Text("Cancel")
                .foregroundStyle(.red)
                .underline()
        }
        
    }
    ToolbarItem(placement: .principal) {
        Text(workoutName)
            .fontWeight(.semibold)
    }
    ToolbarItem(placement: .navigationBarTrailing) {
        Button {
            // 保存内容(所有字段可选,但要求填写名称)
            if(!workoutName.isEmpty) {
                workout.name = workoutName
                workout.startTime = workoutStartTime
                workout.endTime = workoutEndTime
                modelContext.insert(workout)
                dismiss()
            }
            
        } label: {
            Text("Save")
                .font(.headline)
                .underline()
                .foregroundStyle(!workoutName.isEmpty ? .blue : .gray)
                .fontWeight(.semibold)
        }
    }
}

.sheet(isPresented: $showExercieSheet) {
    AddExerciseView(workout: workout)
        .presentationDragIndicator(.visible)
    
}

想问:SwiftUI有没有办法检测Workout的Exercise数组新增元素,自动将其加入全局NavigationPath?

解决方案

方法一:通过Sheet回调直接传递新增Exercise

这是最可靠的方案,无需依赖数组监听,直接在创建完成后手动更新导航路径:

  1. 修改AddExerciseView,添加回调闭包传递新增的Exercise:
struct AddExerciseView: View {
    let workout: Workout
    var onExerciseAdded: ((Exercise) -> Void)? // 新增回调
    
    // ... 原有代码 ...
    
    // 创建并添加Exercise时调用回调
    let newExercise = Exercise(id: UUID(),exerciseName: selection!, date: Date.now)
    modelContext.insert(newExercise)
    if let workout = workout {
       workout.exercises?.append(newExercise)
    }
    onExerciseAdded?(newExercise) // 传递新增元素
    dismiss()
}
  1. 在AddWorkoutView中打开Sheet时传入回调,更新导航路径:
.sheet(isPresented: $showExercieSheet) {
    AddExerciseView(workout: workout) { newExercise in
        router.path.append(newExercise) // 直接加入导航路径
    }
    .presentationDragIndicator(.visible)
}

方法二:监听Workout的Exercises数组变化

如果需要通过监听数组变化实现,可以用onChange监听数组更新:

// 在AddWorkoutView的NavigationStack内添加
.onChange(of: workout.exercises) { oldValue, newValue in
    // 对比新旧数组,找到新增的最后一个元素
    if let newExercise = newValue?.last, oldValue?.contains(newExercise) == false {
        router.path.append(newExercise)
    }
}

注意:此方法需要Exercise模型遵循Equatable,否则contains判断会失效:

@Model
class Exercise: Equatable {
    static func == (lhs: Exercise, rhs: Exercise) -> Bool {
        lhs.id == rhs.id // 用唯一ID判断相等
    }
    
    var id: UUID
    var exerciseName: ...
    var date: Date
    // ... 其他属性 ...
    
    init(id: UUID, exerciseName: ..., date: Date) {
        self.id = id
        self.exerciseName = exerciseName
        self.date = date
    }
}

关键提示

  • 方法一更推荐,避免了数组监听可能带来的时序问题
  • 使用方法二时,必须确保Exercise正确实现Equatable
  • 导航路由router需要是全局可访问的状态(比如用@StateObject管理的路由类)

内容的提问来源于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.21 13:08:12