1

I would like to construct a form from an array of fields

class Field : Identifiable {
    let id = UUID()
    var label : String
    var value : String

    init(label: String, value: String) {
        self.label = label
        self.value = value
    }
}
class App: ObservableObject {
@Published var fields : [Field] = [
    Field(label: "PV", value: "100"),
    Field(label: "FV", value: "0"  )]

    func update(label: String) {
        switch label {
            case "PV" : fields[0].value = calcPV()
            case "FV" : fields[1].value = calcFV()
            default: break
        }
    }
}

I am trying to use a ForEach iterator in the View.

struct ContentView: View {
    @EnvironmentObject var app: App
        
    var body: some View {
        Form {
            ForEach(app.fields) { field in
                HStack {
                    Button(action: { self.app.update(label: field.label) } ) {Text(field.label)}
                    TextField("0", text: field.value)  // error
                }
            }
        }
    }
}

This code results in the error message Cannot convert value of type 'String' to expected argument type 'Binding<String>' where I try to pass field.value into TextField().

I haven't been able to find a way to create a Binding to pass to TextField().

I can get the wanted behaviour by explicitly calling TextField("0", text: self.$app.fields[1].value) but want to be able to do this with the ForEach so that I can construct a generic form from an array.

2 Answers 2

3

Here is a solution for such case:

ForEach(Array(app.fields.enumerated()), id: \.1.id) { i, field in
    HStack {
        Button(action: { self.app.update(label: field.label) } ) {Text(field.label)}
        TextField("0", text: self.$app.fields[i].value)
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks @Asperi. That achieves what I wanted. For my specific case I can make the problem a bit simpler if I set an explicit id for the fields, which I post as a separate answer
0

The accepted answer looks like a good general solution. For the specific case, it can be solved by setting an integer id for each Field that corresponds to the position in the array.

class Field :  Identifiable, CustomStringConvertible {
    let id : Int
    var label : String
    var value : String
    
    ...
class App: ObservableObject {
    @Published var fields : [Field] = [
        Field(id: 0, label: "PV", value: "100"),
        Field(id: 1, label: "FV", value: "0" )
    ]
    ...

Then this id can be used in the TextField parameters:

struct ContentView: View {
    @EnvironmentObject var app: App
        
    var body: some View {
        Form {
            ForEach(app.fields) { field in
                HStack {
                    Button(action: { self.app.update(label: field.label) } ) {Text(field.label)}
                    TextField("0", text: self.$app.fields[field.id].value)
                }
            }
        }
    }
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.