How does onDelete() function handler work in a swiftUI List?

Viewed 2673

So I understand that the following code works:

struct ContentView: View {
    func removeRows(at offsets: IndexSet) {
        numbers.remove(atOffsets: offsets)
    }
    
    @State private var numbers = [Int]()
    @State private var currentNumber = 1

    var body: some View {
        VStack {
            List {
                ForEach(numbers, id: \.self) {
                    Text("\($0)")
                }
                .onDelete(perform: removeRows)
            }

            Button("Add Number") {
                self.numbers.append(self.currentNumber)
                self.currentNumber += 1
            }
        }
    }
}

What I don't understand is how the removeRows(at offsets: IndexSet) custom function handler for .onDelete() works. Specifically, why is there an argument label at before offsets:. Could you use on instead of at for example? Is the argument labels just for us to better understand the code?

Then there's IndexSet which I think I understand as a type that holds an Int for indexing purposes or ranges of Ints for use in deleting a row in the list, so that swiftui knows which row to delete?

I think I understand numbers.remove(atOffsets: offsets) is used to actually do the removing of the list row at offsets (in terms of the actual row index being deleted) - but i'm not sure how offsets is actually known by swift? What actually is offsets and how is it's value set?

I'm trying to learn by following this. I still have a very basic knowledge of swiftui, so please explain like I am super noob at this.

1 Answers

If you look at the onDelete signature:

@inlinable public func onDelete(perform action: ((IndexSet) -> Void)?) -> some DynamicViewContent

you can see that it expects a parameter of type ((IndexSet) -> Void)?.

Which means it needs a function accepting IndexSet parameter and returning Void.

Your removeRows function has exactly the same type as needed:

func removeRows(at offsets: IndexSet) { ...

*The above signature is an equivalent of:

func removeRows(at offsets: IndexSet) -> Void { ...

The label at is used for clarity. It could be called someCustomLabel as well:

func removeRows(someCustomLabel offsets: IndexSet) { ...

but then you'd need to call it this way:

removeRows(someCustomLabel: indexSet)

You can also skip the label altogether:

func removeRows(offsets: IndexSet) { ...

and call your function like this:

removeRows(offsets: indexSet)

Note that:

.onDelete(perform: removeRows)

is the same as:

.onDelete(perform: { indexSet in
    self.removeRows(at: indexSet)
})

The IndexSet specifies which indexes to delete. When you swipe left on a row in your List it invokes the onDelete function passing the row index in the IndexSet parameter.

Note that numbers.remove(atOffsets: offsets) expects IndexSet parameter for the atOffsets label (and you can't change this label) but you're free to name your parameter as you want:

func removeRows(at indexSetToDelete: IndexSet) {
    numbers.remove(atOffsets: indexSetToDelete)
}
Related