SwiftUI 的 ForEach
是一个用于动态生成视图的结构,它根据集合数据循环渲染子视图。与 List
或 Picker
不同,ForEach
本身不提供容器布局,而是依赖父容器(如 List
、HStack
、VStack
)来决定显示方式。以下是其详细介绍及使用方法:
一、基本用法
1. 遍历简单集合
直接遍历数组并生成视图:
struct ContentView: View {
let fruits = ["苹果", "香蕉", "橙子"]
var body: some View {
VStack {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
}
}
}
id: \.self
:要求集合元素是唯一且可哈希的(如String
、Int
)。- 若数据元素不唯一,会导致运行时错误。
2. 遍历 Range
生成固定数量的视图:
ForEach(0..<5) { index in
Text("第 \(index) 项")
}
二、处理复杂数据
1. 遵循 Identifiable
协议
为自定义类型添加唯一标识:
struct Fruit: Identifiable {
let id = UUID() // 唯一标识
var name: String
}
struct ContentView: View {
let fruits = [Fruit(name: "苹果"), Fruit(name: "香蕉")]
var body: some View {
List {
ForEach(fruits) { fruit in
Text(fruit.name)
}
}
}
}
- 无需显式指定
id
,因为Identifiable
类型已提供id
属性。
2. 手动指定唯一标识
若数据未遵循 Identifiable
,需手动指定 id
:
ForEach(fruits, id: \.name) { fruit in
Text(fruit.name)
}
- 此处假设
name
是唯一的。
三、与容器结合使用
1. 在 List
中动态生成行
List {
ForEach(fruits) { fruit in
Text(fruit.name)
}
}
2. 在 HStack
/VStack
中布局
生成水平或垂直排列的视图:
HStack {
ForEach(1...3, id: \.self) { number in
Text("\(number)")
.padding()
.background(Color.blue)
.cornerRadius(5)
}
}
四、高级用法
1. 条件渲染
结合 if
语句动态控制子视图:
ForEach(fruits) { fruit in
if fruit.isAvailable {
Text(fruit.name)
} else {
Text("缺货: \(fruit.name)")
.foregroundColor(.gray)
}
}
2. 动态过滤数据
使用 filter
预处理数据:
ForEach(fruits.filter { $0.isAvailable }) { fruit in
Text(fruit.name)
}
3. 结合 Binding
数据
在可编辑列表中动态更新数据:
struct ContentView: View {
@State private var fruits = [Fruit(name: "苹果"), Fruit(name: "香蕉")]
var body: some View {
List {
ForEach($fruits) { $fruit in
TextField("名称", text: $fruit.name)
}
}
}
}
五、与 List
的区别
特性 | ForEach | List |
---|---|---|
布局容器 | 无,依赖父容器(如 VStack ) | 自带滚动容器和默认样式 |
性能优化 | 无自动优化,依赖父容器实现 | 默认懒加载,仅渲染可见项 |
交互功能 | 无内置操作(如滑动删除) | 支持滑动删除、移动、分组等 |
动态数据绑定 | 需手动处理数据变化 | 支持 @State 和 @ObservedObject |
六、常见问题及解决
1. 错误:ID
不唯一
- 现象:
ForEach
报错Identifiable
冲突。 - 解决:
- 确保数据源唯一性。
- 使用
id: \.self
或自定义唯一标识。
2. 动态数据不更新
- 现象:数据变化后视图未刷新。
- 解决:
- 使用
@State
或@ObservedObject
包装数据。 - 确保数据遵循
Identifiable
。
- 使用
七、完整示例
struct Fruit: Identifiable {
let id = UUID()
var name: String
var isAvailable: Bool
}
struct ContentView: View {
@State private var fruits = [
Fruit(name: "苹果", isAvailable: true),
Fruit(name: "香蕉", isAvailable: false),
Fruit(name: "橙子", isAvailable: true)
]
var body: some View {
NavigationStack {
List {
Section(header: Text("水果列表")) {
ForEach($fruits) { $fruit in
HStack {
Text(fruit.name)
Spacer()
Toggle("有货", isOn: $fruit.isAvailable)
}
}
}
Section(header: Text("统计")) {
Text("有货商品:\(fruits.filter { $0.isAvailable }.count) 个")
}
}
.navigationTitle("库存管理")
}
}
}
八、关键总结
- 核心作用:根据集合数据动态生成视图。
- 唯一标识:必须确保每个元素有唯一的
id
。 - 灵活组合:可与任何布局容器(如
List
、HStack
)结合使用。 - 性能注意:在超长列表中,优先使用
List
或LazyVStack
优化性能。
通过 ForEach
,你可以高效处理动态数据渲染,结合 SwiftUI 的声明式语法,快速构建复杂的交互界面。