将 ForEach 与绑定数组一起使用 (SwiftUI)
Using ForEach with a an array of Bindings (SwiftUI)
我的objective是从JSON动态生成一个表单。除了生成 FormField 视图(基于 TextField)并绑定到动态生成的视图模型列表之外,我已经把所有东西放在一起了。
如果我将 FormField 视图换成普通文本视图,它工作正常(见屏幕截图):
ForEach(viewModel.viewModels) { vm in
Text(vm.placeholder)
}
对于
ForEach(viewModel.viewModels) { vm in
FormField(viewModel: $vm)
}
我试图将 ConfigurableFormViewModel
的 viewModels
属性 设为 @State 变量,但它失去了可编码性。 JSON > Binding<[FormFieldViewModel] 自然不行。
这是我的 code 的要点:
尝试不同的方法。 FormField 维护它自己的内部状态并在其文本提交时发布(通过完成):
struct FormField : View {
@State private var output: String = ""
let viewModel: FormFieldViewModel
var didUpdateText: (String) -> ()
var body: some View {
VStack {
TextField($output, placeholder: Text(viewModel.placeholder), onCommit: {
self.didUpdateText(self.output)
})
Line(color: Color.lightGray)
}.padding()
}
}
ForEach(viewModel.viewModels) { vm in
FormField(viewModel: vm) { (output) in
vm.output = output
}
}
解决方案如下:
ForEach(viewModel.viewModels.indices, id: \.self) { idx in
FormField(viewModel: self.$viewModel.viewModels[idx])
}
您可以尝试的第一件事是:
ForEach(0 ..< numberOfItems) { index in
HStack {
TextField("PlaceHolder", text: Binding(
get: { return items[index] },
set: { (newValue) in return self.items[index] = newValue}
))
}
}
之前的方法的问题是,如果 numberOfItems
是动态的并且可能会因为按钮的操作而改变,例如,它不会起作用,它会抛出以下内容错误:ForEach<Range<Int>, Int, HStack<TextField<Text>>> count (3) != its initial count (0). 'ForEach(_:content:)' should only be used for *constant* data. Instead conform data to 'Identifiable' or use 'ForEach(_:id:content:)' and provide an explicit 'id'!
如果你有那个用例,你可以做这样的事情,即使项目在 SwiftView 的生命周期中增加或减少,它也会起作用:
ForEach(items.indices, id:\.self ){ index in
HStack {
TextField("PlaceHolder", text: Binding(
get: { return items[index] },
set: { (newValue) in return self.items[index] = newValue}
))
}
}
花了一些时间来找出这个难题的解决方案。恕我直言,这是一个重大遗漏,尤其是 SwiftUI 应用程序提出在 struct
中具有模型并使用 Binding
检测更改的文档。
它不可爱,而且它需要很多 CPU 时间,所以我不会将它用于大型数组,但这实际上有预期的结果,并且,除非有人指出错误,否则它遵循 ForEach
限制的意图,即仅在 Identifiable
元素相同时才重用。
ForEach(viewModel.viewModels) { vm in
ViewBuilder.buildBlock(viewModel.viewModels.firstIndex(of: zone) == nil
? ViewBuilder.buildEither(first: Spacer())
: ViewBuilder.buildEither(second: FormField(viewModel: $viewModel.viewModels[viewModel.viewModels.firstIndex(of: vm)!])))
}
作为参考,ViewBuilder.buildBlock
习语可以在 body
元素的根部完成,但如果您愿意,可以将其与 if
.[=18 放在一起=]
Swift 5.5
从Swift5.5版本开始,你可以像这样传入bindable直接使用绑定数组
ForEach($viewModel.viewModels, id: \.self) { $vm in
FormField(viewModel: $vm)
}
我的objective是从JSON动态生成一个表单。除了生成 FormField 视图(基于 TextField)并绑定到动态生成的视图模型列表之外,我已经把所有东西放在一起了。
如果我将 FormField 视图换成普通文本视图,它工作正常(见屏幕截图):
ForEach(viewModel.viewModels) { vm in
Text(vm.placeholder)
}
对于
ForEach(viewModel.viewModels) { vm in
FormField(viewModel: $vm)
}
我试图将 ConfigurableFormViewModel
的 viewModels
属性 设为 @State 变量,但它失去了可编码性。 JSON > Binding<[FormFieldViewModel] 自然不行。
这是我的 code 的要点:
尝试不同的方法。 FormField 维护它自己的内部状态并在其文本提交时发布(通过完成):
struct FormField : View {
@State private var output: String = ""
let viewModel: FormFieldViewModel
var didUpdateText: (String) -> ()
var body: some View {
VStack {
TextField($output, placeholder: Text(viewModel.placeholder), onCommit: {
self.didUpdateText(self.output)
})
Line(color: Color.lightGray)
}.padding()
}
}
ForEach(viewModel.viewModels) { vm in
FormField(viewModel: vm) { (output) in
vm.output = output
}
}
解决方案如下:
ForEach(viewModel.viewModels.indices, id: \.self) { idx in
FormField(viewModel: self.$viewModel.viewModels[idx])
}
您可以尝试的第一件事是:
ForEach(0 ..< numberOfItems) { index in
HStack {
TextField("PlaceHolder", text: Binding(
get: { return items[index] },
set: { (newValue) in return self.items[index] = newValue}
))
}
}
之前的方法的问题是,如果 numberOfItems
是动态的并且可能会因为按钮的操作而改变,例如,它不会起作用,它会抛出以下内容错误:ForEach<Range<Int>, Int, HStack<TextField<Text>>> count (3) != its initial count (0). 'ForEach(_:content:)' should only be used for *constant* data. Instead conform data to 'Identifiable' or use 'ForEach(_:id:content:)' and provide an explicit 'id'!
如果你有那个用例,你可以做这样的事情,即使项目在 SwiftView 的生命周期中增加或减少,它也会起作用:
ForEach(items.indices, id:\.self ){ index in
HStack {
TextField("PlaceHolder", text: Binding(
get: { return items[index] },
set: { (newValue) in return self.items[index] = newValue}
))
}
}
花了一些时间来找出这个难题的解决方案。恕我直言,这是一个重大遗漏,尤其是 SwiftUI 应用程序提出在 struct
中具有模型并使用 Binding
检测更改的文档。
它不可爱,而且它需要很多 CPU 时间,所以我不会将它用于大型数组,但这实际上有预期的结果,并且,除非有人指出错误,否则它遵循 ForEach
限制的意图,即仅在 Identifiable
元素相同时才重用。
ForEach(viewModel.viewModels) { vm in
ViewBuilder.buildBlock(viewModel.viewModels.firstIndex(of: zone) == nil
? ViewBuilder.buildEither(first: Spacer())
: ViewBuilder.buildEither(second: FormField(viewModel: $viewModel.viewModels[viewModel.viewModels.firstIndex(of: vm)!])))
}
作为参考,ViewBuilder.buildBlock
习语可以在 body
元素的根部完成,但如果您愿意,可以将其与 if
.[=18 放在一起=]
Swift 5.5
从Swift5.5版本开始,你可以像这样传入bindable直接使用绑定数组
ForEach($viewModel.viewModels, id: \.self) { $vm in
FormField(viewModel: $vm)
}