2

I have problems with deleting cells that contain a Toggle.

My model looks like this:

class Model: ObservableObject {
    @Published var items: [Item]
    
    init(items: [Item]) {
        self.items = items
    }
}

struct Item: Identifiable {
    var id = UUID()
    var text: String
    var isImportant: Bool
}

And my views are these:

struct ContentView: View {
    @EnvironmentObject var model: Model

    var body: some View {
        List {
            ForEach(model.items) {item in
                ItemCell(item: item).environmentObject(self.model)
            }
        .onDelete(perform: deleteItem)
        }
    }
    
    func deleteItem(indexSet: IndexSet) {
        indexSet.forEach({model.items.remove(at: $0)})
    }
}

struct ItemCell: View {
    @EnvironmentObject var model: Model
    var item: Item
    var itemIndex: Int {model.items.firstIndex(where: {$0.id == item.id})!}

    var body: some View {
        Toggle(isOn: $model.items[itemIndex].isImportant) {
            Text(item.text)
        }
    }
}

As you can see I use @EnvironmentObject. Every time I try to delete a cell, I get this error message shown in the AppDelegate:

Thread 1: Fatal error: Index out of range

I assume the problem is how I pass the data from my ContentView() to the ItemCell(). I also tried to integrate the code of ItemCell() into the closure of the ForEach but this didn't work.

Hope someone can help me.

Additional question: What is the purpose of injection with EnvironmentObject (.environmentObject(self.model))? I don't understand when to use it and when not. In my understanding the EnvironmentObject is a global object that lives in the environment and has always updated information, independent on the View.

Thanks! Nico

UPDATE:

I had another idea, that didn't work either: ContentView:

ForEach(model.items.indices) {index in
  ItemCell(item: self.$model.items[index]).environmentObject(self.model)
}

and ItemCell:

@Binding var item: Item
  var body: some View {
    Toggle(isOn: $item.isImportant) {
      Text(item.text)
  }
}

Any ideas?

UPDATE 2

Also this didn't work:

ForEach(Array(model.items.enumerated()), id: \.element) {index, item in
  ItemCell(item: self.$model.items[index]).environmentObject(self.model)
}
Nico
  • 213
  • 1
  • 7

1 Answers1

0

A possible solution is to use a default value instead of force-unwrapping. When you delete a row in the list, its ItemCell view may still exist in memory. By using a default value you can prevent crashing (there's no risk of manipulating the wrong item as you can't access this view anymore).

var itemIndex: Int { model.items.firstIndex(where: { $0.id == item.id }) ?? 0 }

Alternatively, you can use an optional index:

struct ItemCell: View {
    @EnvironmentObject var model: Model
    var item: Item
    var itemIndex: Int? { model.items.firstIndex(where: { $0.id == item.id }) }

    var body: some View {
        Group {
            if itemIndex != nil {
                Toggle(isOn: $model.items[itemIndex!].isImportant) {
                    Text(item.text)
                }
            }
        }
    }
}
pawello2222
  • 23,992
  • 12
  • 59
  • 94