Swipe Actions in SwiftUI 3
The Ultimate Guide to SwiftUI List Views - Part 4
In this part of The Ultimate Guide to List Views, we will look at Swipe Actions. Swipe Actions are used in many apps, most prominently in Apple’s own Mail app. They provide a well-known and easy-to-use UI affordance to allow users to perform actions on list items.
UIKit has supported Swipe Actions since iOS 11, but SwiftUI didn’t support Swipe Actions until WWDC 2021.
In this post, we will look at the following features:
- Swipe-to-delete using the
- Deleting and moving items using
- Using Swipe Actions (this is the most flexible approach, which also gives us a wealth of styling options)
This feature was available in SwiftUI right from the beginning. It is pretty straight-forward to use, but also pretty basic (or rather inflexible). To add swipe-to-delete to a
Listview, all you need to do is apply the
onDelete modifier to a
ForEach loop inside a
List view. This modifier expects a closure with one parameter that contains an
IndexSet, indicating which rows to delete.
Here is a code snippet that shows a simple
List with an
onDelete modifier. When the user swipes to delete, the closure will be called, which will consequently remove the respective row from the array of items backing the
It’s actually quite convenient that
onDelete passes an
IndexSet to indicate which item(s) should be deleted, as
Array provides a method
remove(atOffsets:) that takes an
It is worth noting that you cannot apply
List directly - you need to use a
ForEach loop instead and nest it inside a
List. I am not entirely sure why the SwiftUI team decided to implement it this way - if you have any clue (or work on the SwiftUI team), please get in touch with me!
Moving and Deleting items using EditMode
For some applications, it makes sense to let users rearrange items by dragging them across the list. SwiftUI makes implementing this super easy - all you need to do is apply the
onMove view modifier to a
List and then update the underlying data structure accordingly.
Here is a snippet that shows how to implement this for a simple array:
Again, this is made easy thanks to
Array.move, which expects exactly the parameters that we receive in
To turn on edit mode for a
List, there are two options:
- Using the
- Using the
Under the hood, both approaches make use of SwiftUI’s environment. The following snippet demonstrates how to use the
EditButton to allow the user to turn on edit mode for the list:
For anything that goes beyond swipe-to-delete and
EditMode, SwiftUI now supports Swipe Actions. This new API gives us a lot of control over how to display swipe actions:
- We can define different swipe actions per row
- We can specify the text, icon and tint color to use for each individual action
- We can add add actions to the leading and trailing edge of a row
- We can enable of disable full swipe for the first action on either end of the row, allowing users to trigger the action by completely swiping the row to the respective edge
Basic Swipe Actions
Let’s look at a simple example how to use this new API. To register a Swipe Action for a
List row, we need to call the
swipeActions view modifier. Inside the closure of the view modifier, we can set up one (or more)
Buttons to implement the action itself.
The following code snippet demonstrates how to add a simple swipe action to a
It’s worth noting that the
swipeActions modifier is invoked on the view that represents the row. In this case, it is a simple
Text view, but for more advanced lists, this might as well be a
VStack. This is different from the
onDelete modifier (which needs to be applied to a
ForEach loop inside a
List view) and it gives us the flexibility to apply a different set of actions depending on the row.
Also note that each swipe action is represented by a
Button. If you use any other view, SwiftUI will not register it, and no action will be shown. Likewise, if you try to apply the
swipeActions modifier to the
List or a
ForEach loop, the modifier will be ignored.
Specifying the edge
By default, swipe actions will be added to the trailing edge of the row. This is why, in the previous example, the mark as read/unread action was added to the trailing edge. To add the action to the leading edge (just like in Apple’s Mail app), all we have to do is specify the
edge parameter, like so:
To add actions to either edge, we can call the
swipeActions modifier multiple times, specifying the edge we want to add the actions to.
If you add swipe actions to both the leading and trailing edge, it is a good idea to be explicit about where you want to add the actions. In the following code snippet, we add add one action to the leading edge, and another one to the trailing edge.
You might notice that we used the
role parameter on the
Button to indicate it is
.destructive - this instructs SwiftUI to use a red background colour for this button. We still have to implement deleting the item ourselves, though. And since the
action closure of the
Button is inside the scope of the current row, it is now much easier directly access the current list
item - another advantage of this API design over the previous design for
Swipe Actions and
After reading the previous code snippet, you might be wondering why we didn’t use the
onDelete view modifier instead of implementing a delete action ourselves. The answer is quite simple: as stated in the documentation, SwiftUI will stop synthesising the delete functionality once you use the
Adding more Swipe Actions
To add multiple swipe actions to either edge, we can call the
swipeActions modifier multiple times:
If this makes you feel uneasy, you can also add multiple buttons to the same
swipeActions modifier. The following code snippet results in the same UI as the previous one:
If you add multiple swipe actions to the same edge, they will be shown from the outside in. I.e. the first button will always appear closest to the respective edge.
Please note that, although there doesn’t seem to be any limit as to how many swipe actions you can add to either edge of a row, the number of actions that a user can comfortably use depends on their device. For example, a list row on an iPhone 13 in portrait orientation can fit up to five swipe actions, but they completely fill up the entire row, which not only looks strange, but also leads to some issues when trying to tap the right button. Smaller devices, like an iPhone 6 or even and iPhone 5, can fit even fewer swipe actions. Three or four swipe actions seem to be a sensible limit that should work on most devices.
By default, the first action for any given swipe direction can be invoked by using a full swipe. You can deactivate this behaviour by setting the
allowsFullSwipe parameter to
Styling Your Swipe Actions
As mentioned before, setting the
role of a swipe action’s
.destructive will automatically tint the button red. If you don’t specify a
Button will be tinted in light grey. You can specify any other colour by using the
tint modifier on a swipe action’s
Button - like so:
Button, you can display both text labels and / or icons, using
SwiftUI makes it easy to implement Swipe Actions, a very popular UI pattern that allows users to invoke contextual actions on list items. In comparison to force-touching or long-pressing a list row to invoke a context menu, Swipe Actions offer a more convenient, fluent, way to interact with your app’s UI.
Thanks for reading, and hope to see you again for the final part of the series when we talk about nested lists (a.k.a trees)!