Delete Item from filtered list

Hi Team,

I am trying to delete items from my list but I have a filter on my list.

When I use the normal code to delete its removing the wrong entires:

private func deleteRow(at offsets: IndexSet) {
        budget.remove(atOffsets: offsets)
    }

I have the following filter on my ForEach code:

ForEach($budget.filter{ $budget in
                                return budget.budgetType == "Income" && budget.incomeCategory == budgetCat.categoryDetails})

Let me know if anyone has a solution or other resources I can look at.

Thanks

Inside the ForEach it should say { budget in ….
Not { $budget in …

Hi @mikaelacaron thanks for the response.

My ForEach works fine and as I want so I am just focus on the delete function.

Or do you believe this is causing an issue for delete?

Thanks

I think it can be causing issues. If you take away the $, does your filter still work the same?

When you filter an array, the indices will not match what you started with. Why? Because you are creating a brand new array with its own indices.

import Foundation

//start with an array of Ints
let a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
//print the values
print(a1)                    //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
//and their indices
print(a1.indices.map { $0 }) //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

//filter it to only contain the even numbers
let a2 = a1.filter { $0.isMultiple(of: 2) }
//print the values
print(a2)                    //[2, 4, 6, 8, 10]
//and their indices
print(a2.indices.map { $0 }) //[0, 1, 2, 3, 4]

You can see that in a1, the index 1 has the value 2 but in a2 the index 1 has the value 4. So if you use the indices from a2 to delete an element from a1, you’ll delete the wrong item.

So when you call deleteRow using an IndexSet from your filtered array, you can’t use that value to index into your original array; you’ll get different items back.

When you delete an item from your filtered array, you’ll need to figure out what the index of the item is in the original array and then delete it using that index.

Thanks @roosterboy I do understand that is the case.

Where I am getting stuck is I don’t know what code to use to find the new indices and delete.

Alternatively is there a way to use the items UUID?

Thanks

@mikaelacaron thanks but it did stuff up my code and wouldn’t work anymore.

Thanks

@scott_foley Here’s an example of how you could do it:

import SwiftUI

struct Kid: Identifiable {
    enum Gender: String, CaseIterable {
        case both
        case boy, girl
    }
    
    let id = UUID()
    let name: String
    let age: Int
    let gender: Gender
}

struct Filterama: View {
    @State private var mysteryKids: [Kid] = [
        Kid(name: "Charlotte", age: 11, gender: .girl),
        Kid(name: "Shauna", age: 12, gender: .girl),
        Kid(name: "Mildred", age: 13, gender: .girl),
        Kid(name: "Linton", age: 12, gender: .boy),
        Kid(name: "Jack", age: 12, gender: .boy),
        Kid(name: "Sonny", age: 12, gender: .boy),
    ]
    
    var mysteryGirls: [Kid] {
        mysteryKids.filter { $0.gender == .girl }
    }
    
    var mysteryBoys: [Kid] {
        mysteryKids.filter { $0.gender == .boy }
    }
    
    @State private var genderToShow: Kid.Gender = .both
    
    var filteredKids: [Kid] {
        switch genderToShow {
        case .girl: return mysteryGirls
        case .boy: return mysteryBoys
        default: return mysteryKids
        }
    }
    
    func removeKids(_ idsToDelete: [UUID]) {
        //we are passed an array of UUIDs
        //of the kids we want to remove
        mysteryKids.removeAll { kid in
            //this closure gets called with each Kid
            //in mysteryKids array
            //if idsToDelete contains that Kid's id,
            //remove it from mysteryKids array
            idsToDelete.contains(kid.id)
        }
    }
    
    var body: some View {
        VStack {
            Picker("Genders to Show", selection: $genderToShow) {
                ForEach(Kid.Gender.allCases, id: \.self) { g in
                    Text(g.rawValue).id(g)
                }
            }
            .pickerStyle(.segmented)
            .padding(.horizontal)

            List {
                ForEach(filteredKids) { kid in
                    HStack {
                        Text(kid.name)
                            .bold()
                        Spacer()
                        Text("\(kid.age) years old")
                            .foregroundColor(.gray)
                    }
                }
                .onDelete { idx in
                    //we are passed an IndexSet of the row(s) to delete
                    //map over the IndexSet to get the UUIDs for the
                    //kids to delete
                    let idsToDelete = idx.map { filteredKids[$0].id }
                    //now call removeKids with those IDs
                    removeKids(idsToDelete)
                }
            }
        }
    }
}

@roosterboy that has worked thank you.

I was wondering now if I had that filtered list nested in a sectioned list what would I need to change on the delete code to account for the sections?

Thanks