简体   繁体   中英

SwiftUI observedobject with dynamic fields

I am new to SwiftUI. I have an object that has various properties that I want to bind to fields in the UI. However, the UI is dynamically created. As in, the fields to include and what order can be customized.

Having trouble getting the UI to refresh when the object is updated (for example, when the user enters a zip code, we automatically populate the city and state fields) I have created a simplified sample.

I can get it to work by handling each individual field when creating the TextField , but would like to see if it is possible to be more generic.

Here is the sample object that the UI is displaying the properties

class TestObject: ObservableObject {
    @Published var testForeignKeyId: Int = 0
    @Published var city: String = ""
    @Published var state: String = ""
    @Published var zip: String = ""
    
    func getStringValue(field: String) -> String {
        switch field {
        case "City":
            return self.city
        case "State":
            return self.state
        case "Zip":
            return self.zip
        default:
            return ""
        }
    }
    
    func getIntValue(field: String) -> Int {
        switch field {
        case "TestForeignKeyId":
            return self.testForeignKeyId
        default:
            return 0
        }
    }
}

Sample field struct:

struct TestField: Identifiable {
    let id = UUID().uuidString
    var label: String
    var fieldType: String
}

It works if I do this and handle each field:

struct ContentView: View {
    @ObservedObject var obj: TestObject
    var fields: [TestField] = []
    @State var stringValue = ""
    
    init() {
        self.fields = [TestField(label: "City", fieldType: "Text"), TestField(label: "State", fieldType: "Text"), TestField(label: "Zip", fieldType: "Zip")]
        self.obj = TestObject()
    }

    var body: some View {
        List {
            ForEach(self.fields) { field in
                Text(field.label)
                switch field.label {
                case "City":
                    TextField("", text: $obj.city)
                case "State":
                    TextField("", text: $obj.state)
                case "Zip":
                    TextField("", text: $obj.zip,
                              onEditingChanged: { edit in
                                if !edit {
                                    // lookup values for zip
                                    self.obj.city = "Test"
                                    self.obj.state = "TT"
                                }
                              })
                default:
                    Text(field.label)
                }
            }
        }
    }
}

Would like to be able to do something like this and create the text fields based on the fieldtype instead of the individual fields (since the actual object has many more properties):

                switch field.fieldType {
                case "Text":
                    Text(field.label)
                    TextField("", text: self.obj.getStringValue(field: field.label))
                case "Zip":
                    Text(field.label)
                    TextField("", text: self.obj.getStringValue(field: field.label))

                }

But when I do that, I get the error:

Cannot convert value of type String to expected argument type Binding<String>

I also tried using a state variable like

@State var stringValue = ""

Then in the.onAppear of the text field, I assigned the value of the appropriate property ( obj.state etc) to the stringValue state variable. I thought that maybe the control would refresh if the observedObject was updated, but couldn't figure out where to update the stringValue state variable to get it working.

Hopefully I explained what I'm trying to do well enough. Does anyone have an idea on how to get it working?

Thanks

Well the problem with getStringValue(field: String) function is that it returns a String value (constant) while SwiftUI TextField View takes a Binding for its text parameter.

So, SwiftUI is throwing that error because it's not able to convert automatically a String value to a Binding of that string. One way to solve this is to move your getStringValue(field: String) function into your ContentView and instead of returning a String , you should return a Binding of the string.

So, the function would look like this:

func getStringValue(field: String) -> Binding<String> {
    switch field {
    case "City":
        return self.$obj.city
    case "State":
        return self.$obj.state
    case "Zip":
        return self.$obj.zip
    default:
        return .constant("")
    }
}

At this point, you should not have that error again and the code should compile correctly.

Of course, you can still improve this by using a Struct and enum instead of a hard-coded string values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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