swipeActions used to work only inside List, which made swipe-to-delete the last reason to
choose List over a ScrollView with lazy stacks. The swipeActionsContainer modifier lifts
that restriction: any container can host swipe actions now.
The classic List form
Per row, swipeActions takes an edge and allowsFullSwipe. List enables the behavior
itself, so no container modifier is needed there.
List {
ForEach(messages, id: \.id) { message in
Text(message.content)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button("Delete", role: .destructive) {
messages.removeAll { $0.id == message.id }
}
}
}
}
Enable it anywhere with swipeActionsContainer
Attach swipeActions to the rows as before, and add swipeActionsContainer() on the outer
container. Without it, the row modifiers do nothing outside List.
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item)
.swipeActions {
Button("Delete", role: .destructive) { delete(item) }
}
}
}
}
.swipeActionsContainer()
The container modifier is more than a switch. It keeps only one row's actions open at a time,
dismisses open actions when the user scrolls, and dismisses them on a tap outside the active
row, the coordination List always did internally.
Works with custom Layout types too
A custom Layout accepts the same pair: swipeActions on the children,
swipeActionsContainer() on the layout. Any container qualifies once it carries the modifier.
The practical effect: a card-based screen keeps its own layout, styling, and performance while still offering native swipe interactions.