JS_is_awesome18
JS_is_awesome18

Reputation: 1757

How to handle .onDelete for SwiftUI list array with .reversed()

I am attempting to make a basic SwiftUI list in which each new list item is presented at the top of the list. To do this, I appended .reversed() to the array passed into the ForEach loop, represented by viewModel.itemList. I have also set up .onDelete to handle removing of the list items. However, when I delete an item, such as the last item in the list, it instead deletes the last item in the array (the item at the top of the list). How can I configure .onDelete to delete the correct item when the array is reversed?

See my code below. Thanks!

ContentView

struct ContentView: View {
    
    @StateObject var viewModel = ToDoListViewModel()
    @State private var listItemName = ""
        
    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                List {
                    ForEach(viewModel.itemList.reversed()) { item in
                        Text(item.listItem)
                    }.onDelete { index in
                        self.viewModel.itemList.remove(atOffsets: index)
                    }
                }
                
                HStack {
                    TextField("Enter List Item", text: $listItemName)

                    Button(action: {
                        viewModel.addToList(ToDoModel(listItem: listItemName))
                        listItemName = ""
                    }) {
                        Image(systemName: "plus")
                            .font(.largeTitle)
                            .frame(width: 75, height: 75)
                            .foregroundColor(Color.white)
                            .background(Color.blue)
                            .clipShape(Circle())
                    }
                }.frame(minWidth: 100, idealWidth: 150, maxWidth: 500, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
                    .padding(.leading, 16)
                    .padding(.trailing, 16)
            }.navigationBarTitle("To Do List", displayMode: .inline)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Model

struct ToDoModel: Identifiable, Codable {
    var id = UUID()
    var listItem: String = ""
}

ViewModel

class ToDoListViewModel: ObservableObject {
    @Published var itemList = [ToDoModel]()

    func addToList( _ item: ToDoModel) {
        itemList.append(item)
    }
}

Upvotes: 4

Views: 2597

Answers (3)

BPDev
BPDev

Reputation: 877

I find it more intuitive to reverse the index set.

index: 0, 1, 2, 3
index_reversed: 3, 2, 1, 0

3 - index_reversed = index

I do so by creating a new set and inserting items one by one (maybe there is a better way to do it).


ForEach(array.reversed()){
}
.onDelete { indexReversedSet in
    var indexSet = IndexSet()
    let maxIndex = array.count - 1
    for indexReversed in indexReversedSet {
        let index = maxIndex - indexReversed
        indexSet.insert(index)
    }
    array.remove(atOffsets: indexSet)
}

Upvotes: 0

you could also try this approach:

.onDelete { index in
    // get the item from the reversed list
    let theItem = viewModel.itemList.reversed()[index.first!]
    // get the index of the item from the viewModel, and remove it
    if let ndx = viewModel.itemList.firstIndex(of: theItem) {
        viewModel.itemList.remove(at: ndx)
    }
}

Upvotes: 5

jnpdx
jnpdx

Reputation: 52397

Caveat: this may not be the most algorithmically efficient method. However, for simple deleting on a List, it should perform fine.

.onDelete { offsets in
    let reversed = Array(viewModel.itemList.reversed()) //get the reversed array -- use Array() so we don't get a ReversedCollection
    let items = Set(offsets.map { reversed[$0].id }) //get the IDs to delete
    viewModel.itemList.removeAll { items.contains($0.id) } //remove the items with IDs that match the Set
}

Upvotes: 2

Related Questions