What You Will Build
A list where each row supports swipe gestures on both edges: swipe left to reveal Delete and Archive actions, swipe right to reveal a Pin action. The delete action removes the item with animation. This is the standard interaction pattern used in Mail, Messages, and most task management apps on iOS.
Why This Pattern Matters
Swipe actions are the primary way iOS users perform quick operations on list items. Before iOS 15, implementing custom swipe actions required UIKit bridging or complex gesture recognizers. The .swipeActions modifier makes this declarative and native, with automatic styling, haptics, and accessibility support built in.
Key SwiftUI Concepts
- .swipeActions(edge:) — attaches actions to either the leading or trailing edge of a list row.
- Button(role: .destructive) — automatically styles the button red and enables full-swipe-to-delete.
- .tint() — customizes the background color of individual action buttons.
The Complete Implementation
struct CustomSwipeActionDemo: View {
@State private var items = [
"Design System",
"Navigation Flow",
"API Integration",
"Unit Tests",
"Code Review"
]
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text(item)
.padding(.vertical, 8)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
withAnimation {
items.removeAll { $0 == item }
}
} label: {
Label("Delete", systemImage: "trash")
}
Button {
// archive action
} label: {
Label("Archive", systemImage: "archivebox")
}
.tint(.orange)
}
.swipeActions(edge: .leading) {
Button {
// pin action
} label: {
Label("Pin", systemImage: "pin")
}
.tint(.blue)
}
}
}
.navigationTitle("Swipe Actions")
}
}
How Trailing vs Leading Works
The edge: .trailing parameter places actions on the right side, revealed by swiping left. This is where destructive and secondary actions live by convention. The edge: .leading places actions on the left side, revealed by swiping right, typically used for positive actions like pinning or marking as unread.
Tips and Pitfalls
- Button order matters: The first button in a trailing swipe action is the one closest to the content. A destructive button placed first enables the full-swipe-to-delete gesture automatically.
- .destructive role: Only use this for irreversible actions. SwiftUI colors it red and enables the full-swipe gesture. Non-destructive buttons require a longer swipe to activate.
- Animate removal: Always wrap
removeAllorremove(at:)inwithAnimationfor a smooth row collapse. Without it, the list jumps. - Label with systemImage: Use
Labelinstead of justImageorText. The label provides both an icon (shown in compact mode) and text (shown in accessibility mode). - swipeActions replaces onDelete: If you use
.swipeActions, do not also use.onDeleteon the same ForEach. They conflict. - Maximum actions: Apple recommends at most 3 actions per edge. More than that makes the buttons too narrow to tap.
iOS Version: iOS 15+