1

I have a checklist made with LazyVStack, I implemented remove functionality for the checklist items, but for some reason the app crashes with "Index out of range" only when I try to remove the last element

Here is my code:

struct ChecklistView: View {
    // Properties
    // ==========
    @ObservedObject var checklist = Checklist()
    @ObservedObject var viewModel: ChecklistViewModel
    @Binding var checklistItems: [ChecklistItem]
    @State var newItemName = ""
    @State var newChecklistItemViewIsVisible = false
    @State var presentAddNewItem = true
    let offlineMode: Bool

    var body: some View {
        VStack {
            LazyVStack {
                ForEach(checklistItems) { item in
                    HStack {
                        RowView(checklistItem: $checklistItems[item], viewModel: viewModel)
                            .listRowInsets(.init(top: 0, leading: 8, bottom: 0, trailing: 0))
                            .padding(.horizontal, 12)
                            .padding(.top, 12)
                            .padding(.bottom, 4)

                        Button {
                            //Prints the correct index number for the last element in the array, but when I remove the last element, always crashes.
                            print(index)
                            if let index = checklistItems.firstIndex(where: {$0.id == checklistItems[item].id}){
                                checklistItems.remove(at: index)
                            }
                        } label: {
                            Text("X")
                        }
                    }

                    Divider()
                        .frame(width: 311)
                }
            }
        }
        .frame(width: UIScreen.main.bounds.width - 32)
        .background(backgroundSecondary)
        .cornerRadius(16)
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(borderGray, lineWidth: 1)
        )
    }
}
1
  • Given your ForEach(checklistItems), shouldn't item be of type ChecklistItem? But you're subscripting with it as if it's of type Int. This this your actual code? Commented Sep 17, 2021 at 13:30

2 Answers 2

1

The item from your ForEach(checklistItems) { item in is not an index into the array of checklistItems, it is a ChecklistItem. However you are using it as an index in the RowView(checklistItem: $checklistItems[item], .... This is not correct.

Try this approach using indices to fix your issue:

ForEach(checklistItems.indices, id: \.self) { ndx in
    HStack {
        RowView(checklistItem: $checklistItems[ndx], viewModel: viewModel)
            .listRowInsets(.init(top: 0, leading: 8, bottom: 0, trailing: 0))
            .padding(.horizontal, 12)
            .padding(.top, 12)
            .padding(.bottom, 4)

        Button {
            print(ndx)
            checklistItems.remove(at: ndx)
        } label: {
            Text("X")
        }
    }
    Divider().frame(width: 311)
}

You can also use this:

ForEach(Array(zip(checklistItems.indices, checklistItems)), id: \.0) { ndx, item in
Sign up to request clarification or add additional context in comments.

3 Comments

It is highly likely that in this case you don't want to id by index. When you add/remove items from checklistItems, you are making already existing views change id. This breaks the animations, because the view IDs weren't constant. Instead you should conform ChecklistItem to Identifiable and then id by id: \.1.id in your 2nd piece of code, for example.
Looks like both solutions still crash my app on last item deletion
good advice from George. It may "look" like it will crash, but it does not in my tests. To be sure, show us what is in ChecklistItem
-1

I found a solution, I changed my forEach to:

ForEach(checklistItems.indices, id: \.self) { index in
                    HStack {
                        RowView(
                            checklistItem:
                                Binding(
                                    get: { self.checklistItems[index] },
                                    set: { self.checklistItems[index] = $0 }
                                ),
                            viewModel: viewModel
                        )
                        .padding(.leading, 12)

                        if checklistEditMode {
                            Button {
                                checklistItems.remove(at: index)
                            } label: {
                                Image("XCircle")
                                    .resizable()
                                    .frame(width: 18, height: 18)
                            }
                            .padding(.trailing, 12)
                        }
                    }
                    .padding(.horizontal, 12)
                    .padding(.top, 12)
                    .padding(.bottom, 4)

                    Divider()
                        .frame(width: 311)
                }

and it works now with no crashes, Thanks to this

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.