Detecting the Interactive Pop Gesture in SwiftUI


Greetings, traveler!

SwiftUI doesn’t provide a native way to track when the user begins the interactive pop gesture—the swipe from the edge to navigate back. While NavigationStack and NavigationView abstract away UIKit, both still rely on UINavigationController behind the scenes. This opens the door to integrating gesture tracking using UIKit components.

This article outlines how to detect when the interactive pop gesture is in progress using UIViewControllerRepresentable and UIGestureRecognizer.

Why It Matters

There are several reasons why an app might want to detect the interactive pop gesture. For example, you might want to pause ongoing tasks, disable user interaction, trigger animations, or cancel network requests while the user is navigating back. Since SwiftUI doesn’t expose this gesture state, accessing UIKit is the only practical solution.

The Core Idea

We embed an invisible UIViewController into the SwiftUI hierarchy. Once embedded, we access its parent UINavigationController and observe the interactivePopGestureRecognizer or interactiveContentPopGestureRecognizer for iOS 26+ target. When the gesture’s state becomes .began or .changed, we report it back into SwiftUI using a closure.

The Gesture Observer

Here’s a minimal and reusable wrapper for tracking the gesture:

struct PopGestureObserver: UIViewControllerRepresentable {

    class Coordinator: NSObject {
        var onGestureChange: ((Bool) -> Void)?

        @objc func handlePopGesture(_ gesture: UIGestureRecognizer) {
            let isActive = gesture.state == .began || gesture.state == .changed
            onGestureChange?(isActive)
        }
    }
    
    var onGestureChange: ((Bool) -> Void)?

    func makeCoordinator() -> Coordinator {
        let coordinator = Coordinator()
        coordinator.onGestureChange = onGestureChange
        return coordinator
    }

    func makeUIViewController(context: Context) -> UIViewController {
        let controller = UIViewController()

        DispatchQueue.main.async {
            if let navController = controller.navigationController {
                if #available(iOS 26, *) {
                    navController.interactiveContentPopGestureRecognizer?.addTarget(
                        context.coordinator,
                        action: #selector(Coordinator.handlePopGesture(_:))
                    )
                } else {
                    navController.interactivePopGestureRecognizer?.addTarget(
                        context.coordinator,
                        action: #selector(Coordinator.handlePopGesture(_:))
                    )
                }
            }
        }

        return controller
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

This controller is zero-sized and invisible but fully capable of interacting with UIKit’s navigation system.

Using the Observer in a View

You can embed the observer in any screen where you want to track the gesture. Here’s an example:

struct DetailView: View {
    @State private var isPopping = false

    var body: some View {
        VStack(spacing: 20) {
            Text("Detail View")
            if isPopping {
                Text("Pop gesture in progress")
            }
        }
        .background(
            PopGestureObserver { isActive in
                isPopping = isActive
            }
        )
        .navigationTitle("Detail")
    }
}

The gesture state is updated in real-time via the isPopping flag.

Example Navigation Context

To see the gesture in action, embed the detail view in a navigation container:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            NavigationLink("Go to Detail", destination: DetailView())
                .navigationTitle("Home")
        }
    }
}

Notes on Integration

  • The observer relies on the presence of UINavigationController. If you fully replace system navigation with a custom solution, this method won’t apply.
  • Be aware that the interactivePopGestureRecognizer only becomes active when the back button is visible and the screen allows swipe-to-go-back gestures.

Conclusion

Tracking the interactive pop gesture in SwiftUI requires stepping outside the declarative model and into UIKit. While the integration is simple, it provides crucial access to gesture state that SwiftUI doesn’t yet expose. This technique makes it possible to coordinate navigation gestures with in-progress tasks or UI changes with minimal overhead.