WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

Day 37 - Deleting items using onDelete()

Forums > 100 Days of SwiftUI


When deleting items from an array in a ForEach we need to create a function like

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

which takes as argument something of type IndexSet.

and then attached it to the ForEach as an argument to the onDelete modifiers

ForEach(numbers, id: \.self) { Text("\($0)") } .onDelete(perform: removeRows)

Something I am confused about though is where we are passing in our IndexSet typed object. Is this just implicit because we are attaching the onDelete modifier to a ForEach? Is the IndexSet typed object just the indices of whatever the ForEach is looping through?

I've noticed other instances where there appears to be implicit arguments passed into functions like this but I have not fully grasped what the rule governing when I shold expect behavior like this.



SwiftUI calls the function you provide in the perform parameter of onDelete and passes in the IndexSet to you.


I still don't get how this works, how does SwiftUI know to pass in those parameters?


If you select the .onDelete operation in your code, you can then look up its definition (or look at the Developer Documentation for the operation). The definiton determines what the .onDelete requires and expects to receive.

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

or from Developer Documentation

func onDelete(perform action: Optional<(IndexSet) -> Void>) -> some DynamicViewContent

.onDelete requires a parameter to be passed to it that takes an IndexSet and returns Void, the function in this case. The parameter is optional as well.

Calling .onDelete can result in a change, or not, in the view content (DynamicViewContent).

@inlinable just means that the compiler will decide whether to create a call to .onDelete by reference, or to add the full .onDelete function code inline at the call point.


I still don't get how this works, how does SwiftUI know to pass in those parameters?

Because it knows how many rows you have in the list and which rows you have swiped to delete (or deleted in edit mode) and can pass in the index of those rows. So if you have, say, 10 rows and you swipe on row #5, SwiftUI will pass in an IndexSet indicating item #5 (well, which will actually be index #4 since arrays are 0-based in Swift) should be deleted from the collection that backs the list.


I feel your pain.

One thing you might be stubbing your toe on: You may be thinking that ForEach is a swift command similiar to 'for each' or 'do while' in other languages like C++, JAVA, etc. It's not.

Dig deeper! ForEach is a struct in SwiftUI.

Developer Documentation: ForEach

As you know, in SwiftUI structs can have properties, computed properties, enums, and functions.

So the documentation states that .onDelete is a function within the ForEach struct. @greenamberred points out the function's signature.

Back to your confusion. You still wonder how SwiftUI knows how to pass in an IndexSet into its .onDelete function. The documentation states that the ForEach struct creates views on demand. To do this, it must have some internal private var variables to keep track of the views it creates. They are private so you can't see them, nor can you change them.

Yet, the ForEach struct keeps track of every view it creates, along with some other @State properties. So if you TAP on one or three rows, the ForEach struct will update its internal private vars. It's a pretty good guess to think that one of those private vars is a set of views that you have TAPPED. Also a good guess to assume these are stored as an IndexSet.

So, if you then execute the ForEach function named .onDelete(), the ForEach struct obliges. It has an internal set of indices of selected views AND it has the closure you want to execute. As it executes the closure, it is updating its internal variables so that it can redraw the views on demand!

If you like this answer, please be sure to subscribe to Paul's YouTube channel. It helps to drive visits to his excellent videos, and these tutorials.

PS: This is one of the reasons why the id: \.self is a critical parameter in the ForEach struct's initializer. If your ForEach struct generates 15 views, and six of them are named "@twostraws", the .onDelete() method will have a very hard time trying to decide which one of those six views to delete. The id parameter in the struct's init() tells the ForEach struct what element makes each view unique.


@Obelix thankyou, conceptually it begins to make a bit more sense


Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Fernando's book will guide you in fixing bugs in three real, open-source, downloadable apps from the App Store. Learn applied programming fundamentals by refactoring real code from published apps. Hacking with Swift readers get a $10 discount!

Read the book

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.