Handling Swipe Gestures in SwiftUI

SwiftUI, Apple’s modern UI framework, makes it incredibly easy to build interactive and dynamic user interfaces for iOS, macOS, watchOS, and tvOS. A common requirement in many apps is to handle swipe gestures, allowing users to navigate or trigger actions with a simple swipe. This article explores various ways to implement and customize swipe gestures in SwiftUI.

What are Swipe Gestures?

Swipe gestures are touch-based interactions where a user drags their finger across the screen in a horizontal or vertical direction. These gestures are intuitive and commonly used for actions like deleting items, navigating between views, or revealing hidden content.

Why Handle Swipe Gestures?

  • Improved User Experience: Provides intuitive and quick actions.
  • Navigation: Enables easy navigation between screens or views.
  • Contextual Actions: Allows users to reveal hidden options or actions related to a specific item.

How to Implement Swipe Gestures in SwiftUI

SwiftUI offers several ways to detect and handle swipe gestures. Here are some common methods:

Method 1: Using .gesture(_:) with DragGesture

The DragGesture detects dragging motions, which can be used to recognize swipes. You can then attach actions based on the direction and distance of the drag.

Step 1: Basic Implementation

Here’s how to detect a simple horizontal swipe:


import SwiftUI

struct ContentView: View {
    @State private var offset: CGFloat = 0
    @State private var cardColor: Color = .blue

    var body: some View {
        Rectangle()
            .fill(cardColor)
            .frame(width: 300, height: 200)
            .offset(x: offset)
            .gesture(
                DragGesture()
                    .onChanged { gesture in
                        offset = gesture.translation.width
                    }
                    .onEnded { gesture in
                        if gesture.translation.width > 100 {
                            cardColor = .green
                        } else if gesture.translation.width < -100 {
                            cardColor = .red
                        }
                        withAnimation {
                            offset = 0
                        }
                    }
            )
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

In this example:

  • @State private var offset: CGFloat = 0 tracks the horizontal offset of the rectangle.
  • DragGesture().onChanged updates the offset based on the drag.
  • DragGesture().onEnded checks the total translation to determine if a swipe occurred and updates the color accordingly. The offset resets with animation.

Method 2: Detecting Swipe Direction with enum

For cleaner handling of different swipe directions, an enum can be used:


import SwiftUI

enum SwipeDirection: String, CaseIterable {
    case left, right, up, down
}

struct SwipeDirectionView: View {
    @State private var swipeMessage: String = "No swipe detected"

    var body: some View {
        VStack {
            Text(swipeMessage)
                .padding()
                .font(.title)

            Rectangle()
                .fill(.yellow)
                .frame(width: 200, height: 200)
                .gesture(
                    DragGesture()
                        .onEnded { gesture in
                            let horizontalAmount = gesture.translation.width
                            let verticalAmount = gesture.translation.height

                            if abs(horizontalAmount) > abs(verticalAmount) {
                                if horizontalAmount > 50 {
                                    swipeMessage = "Swiped Right"
                                } else if horizontalAmount < -50 {
                                    swipeMessage = "Swiped Left"
                                }
                            } else {
                                if verticalAmount > 50 {
                                    swipeMessage = "Swiped Down"
                                } else if verticalAmount < -50 {
                                    swipeMessage = "Swiped Up"
                                }
                            }
                        }
                )
        }
    }
}

struct SwipeDirectionView_Previews: PreviewProvider {
    static var previews: some View {
        SwipeDirectionView()
    }
}

Explanation:

  • An enum SwipeDirection categorizes the possible swipe directions.
  • The DragGesture in .gesture determines the swipe direction based on horizontal and vertical translations.
  • Thresholds (e.g., 50) are used to ensure the drag is significant enough to be considered a swipe.

Method 3: Swipe to Delete (Using .swipeActions)

A common use case is implementing swipe-to-delete functionality, which is easily achieved with .swipeActions (available from iOS 15).


import SwiftUI

struct SwipeToDeleteView: View {
    @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]

    var body: some View {
        List {
            ForEach(items, id: \\.self) { item in
                Text(item)
                    .swipeActions(edge: .trailing) {
                        Button(role: .destructive) {
                            items.removeAll { $0 == item }
                        } label: {
                            Label("Delete", systemImage: "trash")
                        }
                    }
            }
        }
    }
}

struct SwipeToDeleteView_Previews: PreviewProvider {
    static var previews: some View {
        SwipeToDeleteView()
    }
}

Key points:

  • List and ForEach display the list of items.
  • .swipeActions(edge: .trailing) adds swipe actions to each row.
  • Button(role: .destructive) creates a red "Delete" button with a trash icon that removes the item from the list when tapped.

Method 4: Custom Swipe Actions

You can extend the .swipeActions to perform any desired action. Below is an example with edit actions, pinning functionalities using `swipeActions` with SwiftUI lists for enhanced control and design:


import SwiftUI

struct CustomSwipeActionsView: View {
    @State private var items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
    @State private var pinnedItems: Set = []
    
    var body: some View {
        List {
            ForEach(items, id: \\.self) { item in
                Text(item)
                    .swipeActions(edge: .trailing, allowsFullSwipe: false) {
                        Button {
                            // Edit action
                            print("Editing \\(item)")
                        } label: {
                            Label("Edit", systemImage: "pencil")
                        }
                        .tint(.blue)

                        Button(role: .destructive) {
                            // Delete action
                            items.removeAll { $0 == item }
                        } label: {
                            Label("Delete", systemImage: "trash")
                        }
                    }
                    .swipeActions(edge: .leading, allowsFullSwipe: true) {
                        Button {
                            // Pin action
                            if pinnedItems.contains(item) {
                                pinnedItems.remove(item)
                            } else {
                                pinnedItems.insert(item)
                            }
                        } label: {
                            Label(pinnedItems.contains(item) ? "Unpin" : "Pin", systemImage: pinnedItems.contains(item) ? "pin.slash" : "pin")
                        }
                        .tint(pinnedItems.contains(item) ? .gray : .orange)
                    }
            }
        }
    }
}

struct CustomSwipeActionsView_Previews: PreviewProvider {
    static var previews: some View {
        CustomSwipeActionsView()
    }
}

Here:

  • The swipeActions(edge: .trailing) includes actions to "Edit" and "Delete" an item.
  • swipeActions(edge: .leading, allowsFullSwipe: true) includes an actions to either "Pin" and "Unpin" an item based on current pinned status of items
  • allowsFullSwipe: false ensures the buttons remain visible unless explicitly tapped.

Conclusion

SwiftUI provides versatile tools to handle swipe gestures, enhancing user interaction within your apps. Whether it's detecting simple swipes with DragGesture or implementing swipe-to-delete with .swipeActions, understanding these techniques enables you to create more engaging and intuitive interfaces. Customize swipe actions to fit your app's needs and provide seamless navigation and contextual actions.