Byto H
Byto H

Reputation: 73

Fatal error: Index out of range in SwiftUI

I made a practice app where the main view is a simple list. When the item of the list is tapped, it presents the detail view. Inside the detail view is a “textField” to change the items title.

I always have the error by making this steps:

  1. add 3 items to the list
  2. change the title of the second item
  3. delete the third item
  4. delete the second item (the one you changed the title.

When you delete the item that you changed the name, the app will crash and presente me the following error: “Fatal error: Index out of range in SwiftUI”

How can I fix it?

The main view:

struct ContentView: View {
    @EnvironmentObject var store: CPStore
    
    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(0..<store.items.count, id:\.self) { index in
                        NavigationLink(destination: Detail(index: index)) {
                            VStack {
                                Text(self.store.items[index].title)
                            }
                        }
                    }
                    .onDelete(perform: remove)
                }
                Spacer()
                
                Button(action: {
                    self.add()
                }) {
                    ZStack {
                        Circle()
                        .frame(width: 87, height: 87)
                    }
                }
            }
            .navigationBarTitle("Practice")
            .navigationBarItems(trailing: EditButton())
        }
    }
    
    func remove(at offsets: IndexSet) {
        withAnimation {
            store.items.remove(atOffsets: offsets)
        }
    }
    
    func add() {
        withAnimation {
            store.items.append(CPModel(title: "Item \(store.items.count + 1)"))
        }
    }
}

The detail view:

struct Detail: View {
    @EnvironmentObject var store: CPStore
    let index: Int
    
    var body: some View {
        VStack {
            //Error is here
            TextField("Recording", text: $store.items[index].title)
        }
    }
}

The model:

struct CPModel: Identifiable {
    var id = UUID()
    var title: String
}

And view model:

class CPStore: ObservableObject {
    @Published var items = [CPModel]()
}

Upvotes: 1

Views: 1937

Answers (2)

Jim lai
Jim lai

Reputation: 1409

My guess is that when you delete item index 1, Detail for index 1 is triggered to re-render before ContentView is triggered. Since SwiftUI doesn't know index has to be updated first because index is a value type (independent of anything).

As another answer has already pointed out, give it a self-contained copy should solve the problem.

In general you should avoid saving a copy of the index since you now have to maintain consistency at all times between two sources of truth.

In terms of your usage, index is implying "index that is currently legit", which should come from your observed object.

Upvotes: 1

ryandu
ryandu

Reputation: 647

Instead of getting the number of items in an array and using that index to get items from your array of objects, you can get each item in the foreach instead. In your content view, change your For each to this

ForEach(store.items, id:\.self) { item in
                    NavigationLink(destination: Detail(item: item)) {
                        VStack {
                            Text(item.title)
                        }
                    }
                }

And Change your detail view to this:

struct Detail: View {
    @EnvironmentObject var store: CPStore
    @State var item: CPModel

    var body: some View {
        VStack {
            //Error is here
            TextField("Recording", text: $item.title)
        }
    }
}

Upvotes: 2

Related Questions