繁体   English   中英

将 ForEach 与绑定数组一起使用 (SwiftUI)

[英]Using ForEach with a an array of Bindings (SwiftUI)

我的目标是从 JSON 动态生成一个表单。 除了生成带有绑定到动态生成的视图模型列表的 FormField 视图(基于 TextField)之外,我已经将所有内容放在一起。

如果我将 FormField 视图换成普通的 Text 视图,它就可以正常工作(见截图):

ForEach(viewModel.viewModels) { vm in
    Text(vm.placeholder)
}

为了

ForEach(viewModel.viewModels) { vm in
     FormField(viewModel: $vm)
}

我试图使ConfigurableFormViewModelviewModels属性成为 @State var,但它失去了可编码性。 JSON > Binding<[FormFieldViewModel] 自然不会真正起作用。

这是我的 代码的要点:

来自 JSON 但使用 <code>Text</code> 的表单的屏幕截图

您可以尝试的第一件事是:

ForEach(0 ..< numberOfItems) { index in
   HStack {
     TextField("PlaceHolder", text: Binding(
       get: { return items[index] },
       set: { (newValue) in return self.items[index] = newValue}
     ))
   }
}

前一种方法的问题在于,如果numberOfItems是动态的,并且可能会因 Button 的动作而改变,则它不会起作用,并且会抛出以下错误: 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'! 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}
     ))
   }
}

尝试不同的方法。 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
    }
}

斯威夫特 5.5

从 Swift 5.5 版本开始,您可以通过像这样传入 bindable 来直接使用绑定数组。

ForEach($viewModel.viewModels, id: \.self) { $vm in
     FormField(viewModel: $vm)
}

解决方案可能如下:

ForEach(viewModel.viewModels.indices, id: \.self) { idx in
     FormField(viewModel:  self.$viewModel.viewModels[idx])
}

花了一些时间来找出这个难题的解决方案。 恕我直言,这是一个重大遗漏,尤其是 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一起放置。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM