Custom iOS 26 Full Page Swipe-Back Gesture
UIKit-SwiftUI interpolation in practice

The code in this post is available here.
Introduction
iOS 26 introduced a revolutionary swipe-to-go-back feature that allows users to navigate back from any white space area on the screen, not just from the edges. This gesture enhances user experience by making navigation more intuitive and fluid. However, this article will guide you through implementing a custom version of this behavior for your own applications.
Motivation: Why Implement Custom Swipe-Back?
There are two compelling reasons to implement custom swipe-back gestures in your iOS apps:
Limited iOS 26 Support: The native iOS 26 swipe-to-go-back feature only works when navigation links use the default back button. If you customize the back button—for example, replacing it with a toolbar item like an SF Symbol "xmark" close button—the swipe gesture may not function as expected.
Backward Compatibility: Supporting this behavior on older iOS versions ensures your app remains accessible and user-friendly across all supported devices, not just those running the latest OS version.
Step-by-Step Implementation
The following implementation provides a reusable view modifier that enables full-screen swipe-back gestures on navigation controllers:
Step 0: Create example/caller screen to observe difference beweten native behavior and unsupported use cage
struct ContentView: View {
@State private var showingDetail = false
var body: some View {
NavigationStack {
List {
NavigationLink(destination: DetailView()) {
Text("Default Behavior")
}
NavigationLink(destination: AnotherDetailView()) {
Text("Custom Behavior")
}
}
// .enableSwipeBackGesture() // We'll be building this modifier
}
}
}
struct DetailView: View {
var body: some View {
VStack(spacing: 20) {
Text("Detail Content")
.font(.title2)
Button("Close") {
// Custom close action
}
.buttonStyle(.bordered)
}
.navigationTitle("Detail")
}
}
struct AnotherDetailView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Text("Another Detail View")
.navigationTitle("Another")
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button {
dismiss()
} label: {
Label("Close", systemImage: "xmark")
.labelStyle(.iconOnly)
}
}
}
}
}
This screen contains two navigation links. First one is default behavior, where users can navigate back to parent view from destination by swiping back from any blank space area. Second doesn't support this bahavior because of custom tool bar item.
Step 1: Create the ViewController Class
final class ViewController: UIViewController, UIGestureRecognizerDelegate {
private var fullScreenSwipeBackGesture: UIPanGestureRecognizer?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
enableSwipeBackIfNeeded()
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
enableSwipeBackIfNeeded()
}
func enableSwipeBackIfNeeded() {
guard let navigationController else {
return
}
navigationController.interactivePopGestureRecognizer?.isEnabled = true
navigationController.interactivePopGestureRecognizer?.delegate = nil
installFullScreenSwipeBackGesture(on: navigationController)
}
}
Step 2: Install the Full-Screen Gesture Recognizer
private func installFullScreenSwipeBackGesture(on navigationController: UINavigationController) {
guard fullScreenSwipeBackGesture == nil else {
return
}
guard
let gestureRecognizer = navigationController.interactivePopGestureRecognizer,
let targets = gestureRecognizer.value(forKey: "targets") as? [NSObject],
let target = targets.first?.value(forKey: "target")
else {
return
}
let panGestureRecognizer = UIPanGestureRecognizer(
target: target,
action: NSSelectorFromString("handleNavigationTransition:")
)
panGestureRecognizer.maximumNumberOfTouches = 1
panGestureRecognizer.delegate = self
panGestureRecognizer.name = "FullScreenSwipeBack"
navigationController.view.addGestureRecognizer(panGestureRecognizer)
fullScreenSwipeBackGesture = panGestureRecognizer
}
Step 3: Implement Gesture Filtering Logic
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let navigationController, navigationController.viewControllers.count > 1 else {
return false
}
guard let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else {
return false
}
let translation = panGestureRecognizer.translation(in: gestureRecognizer.view)
return translation.x > 0 && abs(translation.x) > abs(translation.y)
}
Step 4: Create the UIViewControllerRepresentable Wrapper
private struct SwipeBackGestureEnabler: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ViewController {
ViewController()
}
func updateUIViewController(_ uiViewController: ViewController, context: Context) {
uiViewController.enableSwipeBackIfNeeded()
}
}
Step 5: Add the View Modifier Extension
extension View {
func enableSwipeBackGesture() -> some View {
background(SwipeBackGestureEnabler())
}
}
Example Usage
Here's how to integrate this modifier into your SwiftUI app:
struct ContentView: View {
@State private var showingDetail = false
var body: some View {
NavigationStack {
List {
NavigationLink(destination: DetailView()) {
Text("Default Behavior")
}
NavigationLink(destination: AnotherDetailView()) {
Text("Custom Behavior")
}
}
.enableSwipeBackGesture() // Uncommented this line
}
}
}
Key Components Explained:
UIViewControllerRepresentable: This protocol bridges SwiftUI views with UIKit view controllers, allowing us to manage the lifecycle of our customViewController.UIGestureRecognizerDelegate: By conforming to this protocol, we gain control over gesture recognition behavior through thegestureRecognizerShouldBegin(_:)method.Gesture Filtering Logic: The implementation ensures that swipe gestures only trigger when:
There are multiple view controllers in the navigation stack (preventing accidental dismissals on root views)
The horizontal translation exceeds vertical translation (ensuring it's a swipe, not a tap or scroll)
Full-Screen Coverage: Unlike edge-based swipes, this implementation captures gestures anywhere on the screen by adding the gesture recognizer to the navigation controller's view.
Benefits of This Implementation
Customizable Back Button: You can now use custom toolbar items (like SF Symbols) while maintaining swipe-back functionality
Consistent UX: Users experience the same intuitive navigation pattern regardless of how you configure your navigation UI
Cross-Version Support: Works on iOS versions that don't have native full-screen swipe support built-in
Conclusion
Implementing a custom swipe-back gesture gives you greater control over user navigation in your iOS applications. By using the enableSwipeBackGesture() view modifier, you can seamlessly integrate this feature while maintaining clean, declarative SwiftUI code. This approach ensures your app provides a modern, fluid navigation experience that works consistently across different configurations and iOS versions.
If you found this article helpful, please consider supporting my work on Buy Me a Coffee. Don't forget to subscribe to my newsletter for more tutorials like this!



