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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.