Faruk
Faruk

Reputation: 2449

SwiftUI .onDelete throws Fatal Error: Index out of range

I have list of Accounts that each of them has a detail page and they are connected through @Binding in the AccountDetailView. Current code work well, updates are fine. No problem at all. However when I added the onDelete modifier to the ForEach below and tried swipe to delete gesture in app, it crashes and says Fatal Error: Index out of range twice.

I made some search and learned that ForEach somehow does not get notified -or ignores it, idk much detail- and looks for the last index of the array. In the end, it can not find it and throws the error.

List {
    Section(header: Text(String.empty), footer: Text(Strings.SectionFooters.accountListFooter.value)) {
        ForEach(self.accountsModel.accounts.indices, id:\.self) { idx in
            NavigationLink(destination: AccountDetailView(account: self.$accountsModel.accounts[idx])) {
                AccountRow(account: self.$accountsModel.accounts[idx])
            }
        }.onDelete { (set) in
            self.accountsModel.accounts.remove(atOffsets: set)
        }
    }
}

With keeping id: \.self parameter in place it throws the error at the AppDelegate, when I try to remove the parameter, the app works fine but again onDelete it throws the same error at NavigationLink's row above.

Here is the AccountDetailView

struct AccountDetailView: View {

    @EnvironmentObject var accountsModel: AccountsViewModel
    @Binding var account: Account
    @State var isEditing: Bool = false

    var body: some View {
        ...
    }
}

Finally Account class conforms to Codable, NSObject, Indentifiable and some other class. I did not want to give all the code just because did not want make the question complicated and hard to examine. If requested, I will provide any part of the code. Thanks in advance.

Upvotes: 0

Views: 971

Answers (1)

Faruk
Faruk

Reputation: 2449

Found a solution. With respect to the this answer to a related question, it came clear. Apperantly, with using the .indices to fullfill ForEach makes onDelete not to work. Instead, going through directly Elements of an array and creating proxy Bindings is working.

To be it more clear here are the ContentView, DetailView and an extension for Binding to avoid cluttering view.

ContentView

struct ContentView: View {

    @EnvironmentObject var store: DataStore

    var body: some View {
        NavigationView {
            List {
                ForEach(self.store.data, id: \.id /*with or without id*/) { (data) in
                    NavigationLink(destination: DetailView(data: /*created custom initializer*/ Binding(from: data))) {
                        Text(data.name)
                    }
                }.onDelete { (set) in
                    self.store.data.remove(atOffsets: set)
                }
            }.navigationBarTitle("List")
        }
    }
}

DetailView

struct DetailView: View {

    @Binding var data: MyData

    var body: some View {
        List {
            TextField("name", text: self.$data.name)
        }
        .navigationBarTitle(self.data.name)
        .listStyle(GroupedListStyle())
    }
}

Binding extension

extension Binding where Value: MyData {
    init(from data: MyData) {
        self.init(get: {
            data as! Value
        }) {
            dataStore.data[dataStore.data.firstIndex(of: data)!] = $0
        }
    }
}

This way, both onDelete and publishing changes by directly updating the object inside detail view will work.

Upvotes: 2

Related Questions