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

如何在ForEach中从Published属性获取Binding

解决动态按钮选中状态绑定问题

问题核心

当前代码中ButtonViewModel是引用类型的ObservableObject,在ForEach遍历数组时,无法直接将vm.$isSelected作为@Binding传递给CustomButton——SwiftUI无法自动将引用类型的@Published属性转换为Binding,且数组元素的状态更新无法正确触发视图刷新。

方案1:将ButtonViewModel改为值类型(推荐)

把ButtonViewModel从class改成struct,利用值类型特性结合@Published数组,轻松实现绑定:

struct SomeView: View {
  @StateObject var viewModel: SomeViewModel
  var body: some View {
    VStack(alignment: .leading, spacing: 12) {
      // 直接遍历绑定数组,拿到每个元素的Binding
      ForEach($viewModel.buttonViewModels, id: \.id) { $vm in
        CustomButton(isSelected: $vm.isSelected)
      }
    }
  }
}

class SomeViewModel: ObservableObject {
  @Published var buttonViewModels: [ButtonViewModel] = []
}

// 改为struct,无需ObservableObject
struct ButtonViewModel: Identifiable {
  var isSelected: Bool
  let id: String = UUID().uuidString

  init(isSelected: Bool) {
    self.isSelected = isSelected
  }
}

public struct CustomButton: View {
  @Binding public var isSelected: Bool

  public var body: some View {
    Button {
      isSelected.toggle()
    } label: {
      Text(isSelected ? "已选中" : "未选中")
    }
  }
}

该方案优势:

  • 代码简洁,无需维护多个ObservableObject
  • 值类型更新会自动触发@Published数组刷新,视图响应更可靠
  • 语法直观,直接通过$获取元素绑定

方案2:保留class类型,通过索引手动创建绑定

如果必须保留ButtonViewModel为class,可以通过数组索引手动构建Binding:

struct SomeView: View {
  @StateObject var viewModel: SomeViewModel
  var body: some View {
    VStack(alignment: .leading, spacing: 12) {
      // 遍历带索引的数组元素
      ForEach(Array(viewModel.buttonViewModels.enumerated()), id: \.element.id) { index, _ in
        CustomButton(isSelected: Binding(
          get: { viewModel.buttonViewModels[index].isSelected },
          set: { viewModel.buttonViewModels[index].isSelected = $0 }
        ))
      }
    }
  }
}

class SomeViewModel: ObservableObject {
  @Published var buttonViewModels: [ButtonViewModel] = []
}

class ButtonViewModel: Identifiable {
  @Published var isSelected: Bool
  let id: String = UUID().uuidString

  init(isSelected: Bool) {
    self.isSelected = isSelected
  }
}

public struct CustomButton: View {
  @Binding public var isSelected: Bool

  public var body: some View {
    Button {
      isSelected.toggle()
    } label: {
      Text(isSelected ? "已选中" : "未选中")
    }
  }
}

注意事项:

  • 需要让ButtonViewModel遵循Identifiable协议,确保ForEach能稳定识别元素
  • 要保证索引对应的元素始终存在(避免数组元素被删除/重排后索引失效)
  • 该方式代码相对繁琐,状态更新的可靠性不如值类型方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 13:17:15