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 removeAll or remove(at:) in withAnimation for a smooth row collapse. Without it, the list jumps.
  • Label with systemImage: Use Label instead of just Image or Text. 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 .onDelete on 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+

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.