Swipe actions are quite common in apps, as they provide quick access to operations that are reachable simply by dragging a single finger. Users are well acquainted with them, and used to finding them in apps that list items of various kinds. For instance, they know how to swipe from the trailing to the leading edge in order to delete an item, and how to do the exact opposite movement to reveal a short list of other actions. Each app brings its own collection of swipe actions, depending of course on its needs and functionality.
Note
Find out how to integrate swipe actions in SwiftUI apps in this previous tutorial.
Swipe actions have traditionally been a capability attachable only to List items in SwiftUI. However, this changed as of iOS 27 (and the respective versions in other systems). We can now define swipe actions in items enumerated not only in lists, but also in other container views, usually inside scroll views. That's possible thanks to a new modifier named swipeActionsContainer. In this post we're going to meet it, and find out how to provide swipe actions in custom items presented in a grid.
Preparing the sample app
The sample app in this post has a simple goal; to demonstrate how to enable swipe actions in items contained in a vertical grid. That is, a LazyVGrid contained in a ScrollView.
So, our vertical grid here will display a collection of color cards; colored views containing the color name too. The purpose is to enable swipe actions, so each color card allows to:
- swipe from the leading towards the trailing edge and mark or unmark a color as favorite,
- swipe from the trailing towards the leading edge to delete a color from the collection.
Here's what we'll end up with:
In code now, the following represents a color item that will be shown in color cards:
struct ColorItem: Identifiable {
var id = UUID()
var name: String
var color: Color
var isFavorite = false
}
Conforming to Identifiable protocol makes it easy to enumerate ColorItem elements in a ForEach next. The other properties shown above regard the name, the actual color and the favorite state.
In the view we can define a collection of ColorItems:
struct ContentView: View {
@State private var colors: [ColorItem] = [
ColorItem(name: "Red", color: .red),
ColorItem(name: "Orange", color: .orange),
ColorItem(name: "Yellow", color: .yellow),
ColorItem(name: "Green", color: .green),
ColorItem(name: "Mint", color: .mint),
ColorItem(name: "Teal", color: .teal),
ColorItem(name: "Cyan", color: .cyan),
ColorItem(name: "Blue", color: .blue),
ColorItem(name: "Indigo", color: .indigo),
ColorItem(name: "Purple", color: .purple),
ColorItem(name: "Pink", color: .pink),
ColorItem(name: "Brown", color: .brown)
]
...
}
Inside the body of the view, we'll embed a LazyVGrid into ScrollView. Inside the LazyVGrid, a ForEach container will layout all color cards:
struct ContentView: View {
...
private let columns = [GridItem(.adaptive(minimum: 140), spacing: 20)]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 24) {
ForEach(colors) { item in
ColorCard(item: item)
}
}
.padding()
}
}
}
The ColorCard view shown in the above code is the following:
struct ColorCard: View {
let item: ColorItem
var body: some View {
ZStack(alignment: .topTrailing) {
VStack {
RoundedRectangle(cornerRadius: 12)
.fill(item.color.gradient)
.frame(height: 100)
Text(item.name)
.font(.headline)
}
if item.isFavorite {
Image(systemName: "star.fill")
.foregroundStyle(.black.secondary)
.padding(6)
}
}
}
}
As you see, a ZStack hosts a VStack with the color and its name, and if the given ColorItem value is marked as favorite, it also shows a star to the top-trailing side.
So far we met no new code. Everything demonstrated up until here simply builds the grid and the color cards. But with these in place, we can now focus on the swipe actions, and most importantly, how we can enable them in a container view that is not a List.
Adding the swipe actions
Swipe actions can be more than one on the leading or the trailing edge. Here we'll keep it simple and have one action on each edge though.
We define swipe actions with the swipeActions modifier, the details of which are explained in my previous tutorial. What we want here is to enable them on each color card, so the ColorCard view inside the ForEach is where we'll apply the swipeActions modifier. We'll do that twice; one time for the leading action, and one more for the trailing action:
ColorCard(item: item)
.swipeActions(edge: .leading) {
Button("Favorite", systemImage: "star") {
guard let index = colors.firstIndex(where: { $0.id == item.id }) else { return }
colors[index].isFavorite.toggle()
}
}
.swipeActions(edge: .trailing) {
Button("Remove", systemImage: "trash", role: .destructive) {
colors.removeAll(where: { $0.id == item.id })
}
}
Swipe actions are actually buttons, and we work with them like we do when using them in any other place. More buttons in a swipeActions modifier would add more actions on the one or the other side.
The above code adds the swipe actions, but they are not enabled yet. There's one last thing missing, and that's the new API as of iOS 27 that Apple presented in WWDC 2026.
The swipe actions container modifier
What enables swipe actions on the items of a container view is a single view modifier, called swipeActionsContainer. Most often we apply it to a ScrollView, as it usually embeds other container views, such as a LazyVGrid, LazyVStack, ForEach, or something else. But the presence of the ScrollView is not necessary if the content should not scroll. In any case, swipeActionsContainer ensures that:
- swipe actions are shown on one item only at the time,
- they are dismissed when scrolling or tapping elsewhere,
- they are dismissed when appearing on another item.
Generally, the swipeActionsContainer modifier makes the target container view behave exactly like a List when it comes to swipe actions.
Having said all that, let's add it to our code. We'll apply it to the ScrollView, so the swipe actions we added previously are enabled for all of its content, even content that will appear after scrolling:
struct ContentView: View {
...
var body: some View {
ScrollView {
...
}
.swipeActionsContainer()
}
}
We can now run the app and see swipe actions working on a grid too:
Wrapping up
A single view modifier powers-up other container views with capabilities of a List. Swipe actions in items out of lists have been a missing feature, and it's a really big deal now that it finally exists. Note that swipeActionsContainer is available as of iOS 27 and the respective versions in all other systems, without providing backwards compatibility. If you know how to add swipe actions, then you're just a view modifier away from making them work in views other than lists. Thank you for reading!