Using ForEach with a an array of Bindings (SwiftUI)

The first thing that you can try is this:

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

The problem with the previous approach is that if numberOfItems is some how dynamic and could change because of an action of a Button for example, it is not going to work and it is going to throw the following error: 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'!

If you have that use case, you can do something like this, it will work even if the items are increasing or decreasing during the lifecycle of the 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}
     ))
   }
}

Trying a different approach. The FormField maintains it's own internal state and publishes (via completion) when its text is committed:

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
    }
}

Tags:

Swift

Swiftui