Search code examples
swiftswiftui

SwiftUI ForEach Binding compile time error looks like not for-each


I'm starting with SwiftUI and following WWDC videos I'm starting with @State and @Binding between two views. I got a display right, but don't get how to make back-forth read-write what was not include in WWDC videos.

I have model classes:

class Manufacturer {
    let name: String
    var models: [Model] = []
    
    init(name: String, models: [Model]) {
        self.name = name
        self.models = models
    }
}

class Model: Identifiable {
    var name: String = ""
    
    init(name: String) {
        self.name = name
    }
}

Then I have a drawing code to display that work as expected:

var body: some View {
    VStack {
        ForEach(manufacturer.models) { model in
            Text(model.name).padding()
        }
    }.padding()
}

and I see this: Canvas preview picture

But now I want to modify my code to allows editing this models displayed and save it to my model @Binding so I've change view to:

var body: some View {
    VStack {
        ForEach(self.$manufacturer.models) { item in
            Text(item.name)
        }
    }.padding()
}

But getting and error in ForEach line:

Generic parameter 'ID' could not be inferred

What ID parameter? I'm clueless here... I thought Identifiable acting as identifier here.

My question is then: I have one view (ContentView) that "holds" my datasource as @State variable. Then I'm passing this as @Binding to my ManufacturerView want to edit this in List with ForEach fill but cannot get for each binding working - how can I do that?


Solution

  • First, I'm assuming you have something like:

    @ObservedObject var manufacturer: Manufacturer
    

    otherwise you wouldn't have self.$manufacturer to begin with (which also requires Manufacturer to conform to ObservableObject).

    self.$manufacturer.models is a type of Binding<[Model]>, and as such it's not a RandomAccessCollection, like self.manufacturer.models, which is one of the overloads that ForEach.init accepts.

    And if you use ForEach(self.manufacturer.models) { item in ... }, then item isn't going to be a binding, which is what you'd need for, say, a TextField.

    A way around that is to iterate over indices, and then bind to $manufacturer.models[index].name:

    ForEach(manufacturer.indices) { index in
       TextField("model name", self.$manufacturer.models[index].name)
    }
    

    In addition to that, I'd suggest you make Model (and possibly even Manufacturer) a value-type, since it appears to be just a storage of data:

    struct Model: Identifiable {
      var id: UUID = .init()
      var name: String = ""
    }
    

    This isn't going to help with this problem, but it will eliminate possible issues with values not updating, since SwiftUI wouldn't detect a change.