需求:有一个列表表单,编辑的时候上级页面带进来默认值,在编辑也可以修改。在SwiftUI中如何用ForEach优雅的实现。
直接看代码
import SwiftUI
class MyViewModel: ObservableObject {
@Published var stockItemList: [itemModel]! = [itemModel(name: "张三"), itemModel(name: "李四"), itemModel(name: "王麻子")]
}
struct itemModel {
var id: String = UUID().uuidString
var name: String
}
// 写法1
struct ListView: View {
@StateObject var vm: MyViewModel
var body: some View {
ForEach($vm.stockItemList, id: \.id) { $item in
ItemView(vm: $item)
}
}
}
// 写法2
// struct ListView: View {
// @StateObject var vm: MyViewModel
//
// var body: some View {
// ForEach(vm.stockItemList.indices, id: \.self) { index in
// ItemView(vm: $vm.stockItemList[index])
// }
// }
// }
struct ItemView: View {
@Binding var vm: itemModel
var body: some View {
TextField("请输入", text: $vm.name)
}
}
#Preview {
let vm = MyViewModel()
return ListView(vm: vm)
}
这两种写法其实都可以完成我们的需求。MyViewModel是大的数据模型,里面可以包含其他的业务字段,绑定字段,还有提交函数,或者数据转换等等。
总结:
- 写法1
ForEach($vm.stockItemList, id: \.id) {
$item in ItemView(vm: $item)
}
使用了
$vm.stockItemList,即Binding到stockItemList数组的投影。这里,ForEach通过$符号直接绑定了数组中的每个itemModel实例,允许ItemView中的TextField直接修改每个itemModel实例的name属性。使用id: .id确保了即使数组中的元素顺序改变,视图也能正确地识别并重用。
- 写法2
ForEach(vm.stockItemList.indices, id: \.self) { index in
ItemView(vm: $vm.stockItemList[index])
}
这种写法通过遍历
stockItemList数组的索引来访问每个元素。它使用vm.stockItemList[index]来获取每个元素,并通过$符号创建到该元素的Binding。这种方法同样允许ItemView中的TextField修改每个itemModel实例的name属性。使用id: .self意味着每个索引本身作为唯一标识符,这在大多数情况下是有效的,但如果数组元素顺序发生变化,可能会导致不必要的视图重建。
综上所诉,尽量使用第一种写法