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

SwiftUI嵌套ForEach循环中如何为每个迭代设置独立变量

SwiftUI嵌套ForEach实现独立数据修改的解决方案

问题根源

你当前用单个@State private var reps: Int变量存储次数,所有Stepper共享同一个状态,所以修改任意一个Stepper都会同步改变所有显示的数值。


方案一:修改模型存储每组数据(推荐)

这种方式符合SwiftUI的MVVM设计思想,把每组的次数数据直接存在练习模型里,数据和视图分离,更易维护。

1. 调整Exercise模型

给Exercise添加一个数组属性,专门存储每组的次数,同时确保Exercise遵循Identifiable(方便ForEach使用):

import SwiftUI

class Workout: ObservableObject {
    @Published var exercises: [Exercise] = []
}

struct Exercise: Identifiable, Hashable {
    let id = UUID()
    var nameOfExercise: String
    var amountOfSets: Int
    // 新增数组,每个元素对应一组的次数,初始化时设为全0
    @Published var repsPerSet: [Int]
}

2. 初始化练习数据

创建练习时,确保repsPerSet的长度和amountOfSets一致:

// 示例:创建一个3组的俯卧撑练习
let pushUpExercise = Exercise(
    nameOfExercise: "俯卧撑",
    amountOfSets: 3,
    repsPerSet: Array(repeating: 0, count: 3)
)

3. 修改视图代码

使用绑定语法$workout.exercises让子视图可以修改练习的属性,直接绑定每组对应的数组元素:

struct TrainingView: View { // Swift规范:结构体首字母大写
    @ObservedObject var workout: Workout
    
    var body: some View {
        ForEach($workout.exercises) { $exercise in
            Text(exercise.nameOfExercise)
                .font(.headline)
            
            ForEach(0..<exercise.amountOfSets, id: \.self) { setIndex in
                VStack(alignment: .leading) {
                    Text("\(setIndex + 1) Set")
                    HStack {
                        Text("次数:\(exercise.repsPerSet[setIndex])")
                        Stepper("调整", value: $exercise.repsPerSet[setIndex], in: 0...30)
                    }
                }
                .padding(.vertical, 4)
            }
            .padding(.bottom, 12)
        }
    }
}

方案二:用字典临时存储状态(无需修改原模型)

如果不想改动原有的Exercise模型,可以在视图里用字典来临时存储每组的次数:

struct TrainingView: View {
    @ObservedObject var workout: Workout
    // 字典键:练习ID;字典值:对应每组的次数数组
    @State private var repsDict: [UUID: [Int]] = [:]
    
    var body: some View {
        ForEach(workout.exercises) { exercise in
            Text(exercise.nameOfExercise)
                .font(.headline)
            
            ForEach(0..<exercise.amountOfSets, id: \.self) { setIndex in
                // 提前初始化当前练习的次数数组,避免nil
                let _ = repsDict[exercise.id, default: Array(repeating: 0, count: exercise.amountOfSets)]
                
                VStack(alignment: .leading) {
                    Text("\(setIndex + 1) Set")
                    HStack {
                        Text("次数:\(repsDict[exercise.id]?[setIndex] ?? 0)")
                        Stepper("调整", value: Binding(
                            get: { repsDict[exercise.id]?[setIndex] ?? 0 },
                            set: { newValue in
                                guard var sets = repsDict[exercise.id] else { return }
                                sets[setIndex] = newValue
                                repsDict[exercise.id] = sets
                            }
                        ), in: 0...30)
                    }
                }
                .padding(.vertical, 4)
            }
            .padding(.bottom, 12)
        }
    }
}

方案对比

  • 方案一更适合长期维护,数据存在模型中,视图销毁后数据不会丢失,也方便后续把数据持久化(比如存到本地或服务器)。
  • 方案二更适合快速临时解决问题,无需改动原有模型结构,但数据只存在视图的@State里,视图销毁后数据会丢失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 15:40:37