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

时间:2019-06-25 16:34:08

标签: swift swiftui

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

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

import { FilterPipe } from '../components/search/filter.pipe';

@NgModule({
  declarations: [
      MyApp,
      FilterPipe
  ],
  imports: [...],
})
export class AppModule { }

对于

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

我尝试将ForEach(viewModel.viewModels) { vm in FormField(viewModel: $vm) } 的{​​{1}}属性设置为@State变量,但是它失去了可编码性。 JSON>绑定<[FormFieldViewModel]自然不会真正起作用。

这是我的code的要旨:

Screenshot of form from JSON but using <code>Text</code>

5 个答案:

答案 0 :(得分:1)

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

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'!

如果有该用例,则可以执行以下操作,即使项目在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}
     ))
   }
}

答案 1 :(得分:1)

斯威夫特 5.5

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

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

答案 2 :(得分:0)

尝试其他方法。 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
    }
}

答案 3 :(得分:0)

解决方案可能如下:

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

答案 4 :(得分:0)

花一些时间找出解决这个难题的方法。恕我直言,这是一个主要的遗漏,尤其是在SwiftUI Apps提出的文档中,该文档的模型在struct中,并且使用Binding来检测更改。

它不是很可爱,并且会占用大量CPU时间,因此我不会将其用于大型数组,但这实际上可以达到预期的结果,并且除非有人指出错误,否则它遵循{ {1}}的限制,即仅在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一起使用。

相关问题