0

Here is a minimum reproducible code of my problem. I have a dictionary of categories and against each category I have different item array. I want to pass the item array from dictionary, as binding to ListRow so that I can observer the change in my ContentView. Xcode shows me this error which is very clear Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'Item' conform to 'Identifiable.

The solution shows in this question Iterating through set with ForEach in SwiftUI not using any @State or @Published variable. They are just using it for showing the data. Any work around for this issue ??

struct Item {
    var id = UUID().uuidString
    var name: String
}


struct ListRow {
    
    @Binding var item: Item
    
    var body: some View {
        TextField("Place Holder", text: $item.name)
    }
}

struct ContentView: View {
    
    var categories = ["Bakery","Fruits & Vagetables", "Meat & poultry", "Dairy & Eggs", "Pantry", "Household"]
    @State private var testDictionary: [String: [Item]] = [:]
    
    var body: some View {
        
        VStack(alignment: .leading) {
            
            ForEach(categories, id: \.self) { category in
                Text(category)
                    .font(.system(size: 30))
                ForEach(testDictionary[category]) { item in
                    ListRow(item: item)
                }
            }
            
        }.onAppear(
        addDummyDateIntoDictonary()
        )
    }
    
    func addDummyDateIntoDictonary() {
        for category in categories {
            testDictionary[category] = [Item(name: category + "1"), Item(name: category + "2")]
        }
    }
}
Qazi Ammar
  • 953
  • 1
  • 8
  • 23

1 Answers1

1

One problem is that you didn't make ListRow conform to View.

//           add this
//            ╭─┴──╮
struct ListRow: View {
    @Binding var item: Item

    var body: some View {
        TextField("Place Holder", text: $item.name)
    }
}

Now let's address your main problem.

A Binding is two-way: SwiftUI can use it to get a value, and SwiftUI can use it to modify a value. In your case, you need a Binding that updates an Item stored somewhere in testDictionary.

You can create such a Binding “by hand” using Binding.init(get:set:) inside the inner ForEach.

struct ContentView: View {

    var categories = ["Bakery","Fruits & Vagetables", "Meat & poultry", "Dairy & Eggs", "Pantry", "Household"]
    @State private var testDictionary: [String: [Item]] = [:]

    var body: some View {
        VStack(alignment: .leading) {
            ForEach(categories, id: \.self) { category in
                Text(category)
                    .font(.system(size: 30))
                let items = testDictionary[category] ?? []
                ForEach(items, id: \.id) { item in
                    let itemBinding = Binding<Item>(
                        get: { item },
                        set: {
                            if
                                let items = testDictionary[category],
                                let i = items.firstIndex(where: { $0.id == item.id })
                            {
                                testDictionary[category]?[i] = $0
                            }
                        }
                    )
                    ListRow(item: itemBinding)
                }
            }
        }.onAppear {
            addDummyDateIntoDictonary()
        }
    }

    func addDummyDateIntoDictonary() {
        for category in categories {
            testDictionary[category] = [Item(name: category + "1"), Item(name: category + "2")]
        }
    }
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848