Swipe Actions in SwiftUI

Swipe Actions in SwiftUI
⏱ Reading Time: 7 mins

Swipe actions is not a new concept in iOS or macOS programming. We have been using such actions for years in Apple’s applications, with a quite typical example being the Mail app. Programmatically we know them from the table view in UIKit, where we implement them in order to allow users to perform specific operations on individual cells. In SwiftUI swipe actions had not been available until WWDC 2021; it was there where it was eventually announced that the third release of SwiftUI was going to support such actions natively. In this post we are about to explore this topic, and have a hands-on experience on how to integrate swipe actions in SwiftUI.

A demo scenario

Let’s suppose that we have the next list of old movies:

A list of movies with movie image and title

Credits: Top rated movies from IMDB.

Our goal here is to add swipe actions to both the leading and trailing edges of each row, so we can do the following:

  • Indicate whether the user has watched a movie or not.
  • Indicate whether the user watched that movie alone or with friends, i.e. in a cinema.
  • Remove a movie from the list in case the user does not like it.

Here’s what we actually need to end up with:

Two screenshots of the movies list side by side demonstrating the swipe actions.

For the first two cases, visual indications will be displayed on rows with movies that have been watched, and if so, watched with friends or not. These are going to be images. For the last case, the movie will simply be removed from the list.

The following code implements the list:

Movies is an ObservableObject custom type. It contains the moviesData property marked with the @Published property wrapper, which is an array with the data of each single movie. In addition, it contains a few properties that update a specific movie in the moviesData array according to the task that will be triggered from the swipe actions we’ll add next.

Note: You can find the demo project of this post on Github.

Each movie is represented with the MovieData custom type:

Also, the movieInfo(for:) method shown in the previous code snippet is a @ViewBuilder method that implements the contents of each row:

With all the above as granted, let’s add the first swipe action to the displayed rows of the list.

Adding a swipe action

Adding swipe actions to the rows of a list is possible by applying the swipeActions view modifier on each row’s content. The following code demonstrates that:

In this example, the content of each row is implemented in the movieInfo(for:) method, so that’s where the swipeActions modifier is applied.

Here is the first important detail now. Using the modifier as shown above adds a swipe action to the trailing edge of the row by default. However, we want to make it possible to change the watched state of the movies with a swipe action on the leading edge. To manage that, it’s necessary to specify the edge as an argument:

There is also another argument that we can supply swipeActions with:

The meaning of the allowsFullSwipe is the following:

When it’s true, then a full swipe on the row will perform the first (outermost) action automatically. When false, then the first action should be tapped (or clicked on macOS) as the swipe gesture won’t trigger it. By default it’s true, so we can simply omit it here.

Swipe action’s content

Focusing on the content closure now, here we have to implement one or more buttons that represent and perform the actual actions. The order of the buttons matters, as the first one matches to the outermost action.

That said, right next you can see the first button’s implementation responsible for changing the watched state of the movie that is applied on:

toggleWatched(for:) method will update the watched state of the movie with the given id, and subsequently will trigger the appearance of a respective image to the row. At the same time, the displayed image in the button’s label will vary depending on the watched state of the movie. SF Symbol images are great candidates to use in swipe actions’ buttons, and that’s what I also do here.

Here’s another important detail about the image in the button’s label; when using SF Symbol images, the system will automatically apply the fill variation of the symbol, so the action is emphasized and distinguished easily.

Note that it’s not necessary to use the Label view as the button’s label; you may use an Image view or a Text view instead.

Running the app at this point, here’s what we have managed so far:

Swipe action demo from left to right

See that both the full swipe gesture and the tap on the action’s button have the same result. Also notice how the displayed image of the swipe action gets changed depending on the watched state.

Changing the action’s tint color

Admittedly, we have managed to achieve a great functionality with just a few lines of code. However, you’ll notice in the above running example that the button’s background color is gray. That’s the color used by default, unless we explicitly change it with the tint() view modifier applied to the button; the desired color is passed as argument:

Now we have a different result:

Swipe action with green tint color

Adding another swipe action

You can be sure that adding a second, or a third swipe action is not difficult, and it all starts with a decision; where exactly the new action should appear. Should it be the next one in a collection of actions in a specific edge? Or should it be placed to the other edge?

We’ll see that all, focusing on the first case in this section. For the sake of the demonstration, let’s say that we want to have one more action right next to the previous one which will be toggling whether a movie has been watched with friends or not.

In fact, doing so is quite easy, as all it takes is to add one more button to the content closure of the swipeActions modifier we already have. Keeping in mind that the order of buttons matters, we’ll add the new one after the button defined in the previous part:

toggleWatchedWithFriends(for:) method will operate on the actual data, and doing so will trigger the re-rendering of the row matching to the altered movie. See that once again here we’re specifying a custom tint color with the tint() modifier applied to the button.

Here is the result of the above addition:

Two swipe actions on the leading edge of each row, colored green and indigo

Removing rows with a swipe action

Continuing with the demo app, let’s say that we want to give our users one more functionality; to remove movies from the list that they don’t like or they are not planning to watch.

Let’s do so by adding a new swipe action, but this time to the trailing edge of the row. It would be confusing to have it along with the other two actions.

In order to add swipe actions to a row edge that we have not previously used, it’s necessary to use another swipeActions modifier right after the first one. Since we want the new swipe action to appear in the trailing edge, we’ll do so without passing any parameters to the modifier; we could have done so, but it’s not necessary as trailing is the edge used by default:

Inside the new swipeActions modifier’s content closure we’ll implement one more button that will be removing the selected movie from the data source:

The removeMovie(with:) method in the Movies type will remove the proper movie based on the given id value. The removal of the row will be animated. Note here that instead of providing a specific tint color, I set the button’s role to destructive. By attaching a role to the button, the system will automatically assign a proper tint color; in this case it’s going to be red.

Here is the result of the above addition:

Remove row swipe action from right to left with red tint color

Summary

This quick exploration on the swipe actions in the third release of SwiftUI just comes to its end. You may get the sample project from this repository on Github and try out everything discussed in this post, as well as to see parts of code not presented here. There is no doubt that integrating swipe actions in SwiftUI lists is easy, and there are just a few details to remember when doing so. I hope you enjoyed this post and found it valuable. Thank you for reading it!

Note: This post has also been published on Medium.

If you found this post useful then please consider sharing it! Also, subscribe to my newsletter in order to be notified about everything new published here directly in your inbox, and follow me on Twitter, on YouTube, on Medium and other social media.