Wrong item gets deleted from LazyVGrid

106 views Asked by At

My goal is to create a list like this in SwiftUI

LazyVGrid SwiftUI Swipe to delete

Every time the user taps add, a new row is created and then on swiping to delete, the respective row needs to be deleted.

In order to do that, I start with a very basic model

struct SnagItem: Codable {
    private(set) var reference: Int = 0
    private(set) var details: String = ""
    private(set) var isCompleted: Bool = false
    
    enum CodingKeys: String, CodingKey, CaseIterable {
        case reference = "Reference"
        case details = "Details"
        case isCompleted = "IsCompleted"
    }
}

extension SnagItem: Hashable { }

I use this model within a view model which I wish to bind to my SwiftUI view, this is the view model:

class SnagItemViewModel: ObservableObject {
    private let snagItem: SnagItem
    
    @Published var details: String = ""
    @Published var isCompleted: Bool = false
    
    init(withSnagItem snagItem: SnagItem) {
        self.snagItem = snagItem
        details = snagItem.details
        isCompleted = snagItem.isCompleted
    }
}

I have another view model that keeps track of all the snag items (rows) using an array. This also handles the adding and deletion of items.

class SnagItemsViewModel: ObservableObject {
    @Published var snagItems: [SnagItemViewModel] = [SnagItemViewModel(withSnagItem: SnagItem())]
    
    func getSnagItem(at index: Int) -> SnagItemViewModel {
        snagItems[index]
    }
    
    func addSnagItem() {
        snagItems.append(SnagItemViewModel(withSnagItem: SnagItem()))
    }
    
    func deleteSnagItemRecord(at indexSet: IndexSet) {
        guard let index = indexSet.first else { return }
        snagItems.remove(at: index)
    }
}
@Published var snagItems: [SnagItemViewModel] = [SnagItemViewModel(withSnagItem: SnagItem())]

I've created a SwiftUI view that acts as a container:

struct SnagItemsView: View {
    @StateObject var snagItemsViewModel: SnagItemsViewModel
    
    var body: some View {
        ForEach(0 ..< snagItemsViewModel.snagItems.count, id: \.self) { snagItemIndex in
            let snagItemViewModel = snagItemsViewModel.getSnagItem(at: snagItemIndex)
            
            SnagItemRowView(reference: snagItemIndex + 1,
                            snagItemViewModel: snagItemViewModel)
        }
        .onDelete(perform: delete(at:))
    }
    
    func delete(at indexSet: IndexSet) {
        withAnimation {
            snagItemsViewModel.deleteSnagItemRecord(at: indexSet)
        }
    }
}

Each row then has it's own view:

struct SnagItemRowView: View {
    @State var reference: Int
    @StateObject var snagItemViewModel: SnagItemViewModel
    
    let gridItemColumnConfig = [
        GridItem(.fixed(Constants.Frame.minGridItemWidth)),
        GridItem(.flexible()),
        GridItem(.fixed(Constants.Frame.minGridItemWidth))
    ]
    
    var body: some View {
        LazyVGrid(columns: gridItemColumnConfig, spacing: 8) {
            ForEach(0 ..< SnagItem.CodingKeys.allCases.count, id: \.self) { columnIndex in
                
                let snagItemCategory = SnagItem.CodingKeys.allCases[columnIndex]
                
                if snagItemCategory == .reference {
                    Text("\(reference)")
                } else if snagItemCategory == .details {
                    TextField(snagItemViewModel.localizedText(for: .enterValue),
                              text: $snagItemViewModel.details,
                              axis: .vertical)
                } else {
                    Toggle("", isOn: $snagItemViewModel.isCompleted)
                        .labelsHidden()
                }
            }
        }
    }
}

Everything seems to work fine for the most part.

It starts off like this:

LazyVGrid SwiftUI

When the user taps Add, another row is added and doing this multiple times adds more rows:

LazyVGrid rows and colums matrix SwiftUI

The issues begin, when I try to delete:

LazyVGrid swipe to delete List SwiftUI

As you can see I'm trying to delete row 3, after deletion this is the result is that row 4 gets deleted instead, atleast from a UI perspective:

LazyVGrid column matrix SwiftUI with switch

I printed out the values from my get function and it seems that the view model actually seems to have the correct objects, the SwiftUI View doesn't seem to render the correct data however for some reason:

XCode LazyVGrid SwiftUI

I added a breakpoint in my delete function, to see if the right index was being passed and it seems like everything looks good here:

XCode breakpoint swiftUI lazyVGrid delete index

Finally, when I try to add another row after the deletion process, the old data seems to be added back again:

LazyVGrid SwiftUI rows and columns

And again, when I check the view model, the data seems to be right, but the SwiftUI view shows something else.

What am I doing wrong and how could I fix this ?

0

There are 0 answers