iOS
UINavigationController
back swipe gesture
disable gesture
iOS 7

How to disable back swipe gesture in UINavigationController on iOS 7

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

The interactive back swipe gesture in UINavigationController is controlled by the interactivePopGestureRecognizer property. To disable it for a specific screen, set interactivePopGestureRecognizer?.isEnabled = false in viewWillAppear(_:) and restore it to true in viewWillDisappear(_:). This approach scopes the change to exactly one view controller and prevents the gesture policy from leaking to other screens in the navigation stack.

The Basic Approach: Per-Screen Toggle

The most common and cleanest pattern is to disable the gesture when a specific view controller appears and re-enable it when that view controller disappears.

Swift

swift
1import UIKit
2
3final class CheckoutViewController: UIViewController {
4    override func viewWillAppear(_ animated: Bool) {
5        super.viewWillAppear(animated)
6        navigationController?.interactivePopGestureRecognizer?.isEnabled = false
7    }
8
9    override func viewWillDisappear(_ animated: Bool) {
10        super.viewWillDisappear(animated)
11        navigationController?.interactivePopGestureRecognizer?.isEnabled = true
12    }
13}

Objective-C

objective-c
1@implementation CheckoutViewController
2
3- (void)viewWillAppear:(BOOL)animated {
4    [super viewWillAppear:animated];
5    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
6}
7
8- (void)viewWillDisappear:(BOOL)animated {
9    [super viewWillDisappear:animated];
10    self.navigationController.interactivePopGestureRecognizer.enabled = YES;
11}
12
13@end

Why viewWillAppear, Not viewDidLoad

Using viewDidLoad is wrong for two reasons. First, viewDidLoad runs once when the view controller is first loaded, but the gesture state needs to be set every time the screen becomes visible (including when the user navigates back to it). Second, viewDidLoad has no corresponding "undo" lifecycle method, so there is no natural place to restore the gesture.

Lifecycle MethodRuns WhenPaired With
viewDidLoadView is loaded into memory (once)None
viewWillAppearView is about to become visibleviewWillDisappear
viewDidAppearView became visibleviewDidDisappear

viewWillAppear/viewWillDisappear is the right pair because the gesture policy is tied to visibility, not to object lifetime.

Using a Gesture Recognizer Delegate

For more nuanced control, such as allowing the swipe only when certain conditions are met, implement UIGestureRecognizerDelegate:

swift
1import UIKit
2
3final class FormViewController: UIViewController, UIGestureRecognizerDelegate {
4    private var hasUnsavedChanges = false
5
6    override func viewWillAppear(_ animated: Bool) {
7        super.viewWillAppear(animated)
8        navigationController?.interactivePopGestureRecognizer?.delegate = self
9    }
10
11    override func viewWillDisappear(_ animated: Bool) {
12        super.viewWillDisappear(animated)
13        navigationController?.interactivePopGestureRecognizer?.delegate = nil
14    }
15
16    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
17        // Allow the back swipe only if there are no unsaved changes
18        return !hasUnsavedChanges
19    }
20}

This approach lets you conditionally block the gesture. The user can swipe back when the form is clean, but the gesture is blocked when there are unsaved changes. This is better UX than a blanket disable because it only restricts navigation when there is a real reason.

Restoring the Delegate

Setting the delegate to nil in viewWillDisappear is critical. If you forget, the navigation controller keeps a reference to your delegate, and when another view controller becomes active, the delegate methods still route to your object. This causes unexpected behavior and potential crashes if your view controller has been deallocated.

Subclassing UINavigationController for Global Control

If multiple screens in your app need to disable the back swipe, toggling the recognizer in every view controller becomes tedious. A custom navigation controller can centralize this logic:

swift
1import UIKit
2
3final class AppNavigationController: UINavigationController, UINavigationControllerDelegate {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6        delegate = self
7    }
8
9    func navigationController(
10        _ navigationController: UINavigationController,
11        didShow viewController: UIViewController,
12        animated: Bool
13    ) {
14        // Disable the back swipe for any view controller that adopts this protocol
15        let shouldDisable = viewController is BackSwipeDisabled
16        interactivePopGestureRecognizer?.isEnabled = !shouldDisable
17    }
18}
19
20protocol BackSwipeDisabled {}

Now any view controller that needs to disable the swipe just declares conformance:

swift
final class SignatureViewController: UIViewController, BackSwipeDisabled {
    // No lifecycle code needed for gesture management
}

This pattern scales well and keeps gesture policy declarations close to the view controllers that need them, without scattering lifecycle code.

SwiftUI Integration

In SwiftUI, the navigation controller's gesture recognizer is not directly exposed. To disable the back swipe in a SwiftUI view hierarchy that uses UINavigationController under the hood, you need to reach into UIKit:

swift
1import SwiftUI
2
3struct DisableBackSwipe: UIViewControllerRepresentable {
4    func makeUIViewController(context: Context) -> UIViewController {
5        DisableBackSwipeController()
6    }
7
8    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
9}
10
11final class DisableBackSwipeController: UIViewController {
12    override func viewWillAppear(_ animated: Bool) {
13        super.viewWillAppear(animated)
14        navigationController?.interactivePopGestureRecognizer?.isEnabled = false
15    }
16
17    override func viewWillDisappear(_ animated: Bool) {
18        super.viewWillDisappear(animated)
19        navigationController?.interactivePopGestureRecognizer?.isEnabled = true
20    }
21}
22
23// Usage in a SwiftUI view
24struct CheckoutView: View {
25    var body: some View {
26        VStack {
27            Text("Checkout")
28        }
29        .background(DisableBackSwipe())
30    }
31}

This is a workaround rather than an official API. In a pure SwiftUI NavigationStack, Apple does not provide a public API to disable the back swipe as of iOS 17. The UIKit interop approach is the most reliable solution.

Handling Edge Cases

The Frozen Navigation Bug

If the user begins a back-swipe gesture but cancels it (lifts their finger before completing the swipe) on a screen where the back button is hidden, the navigation controller can enter a frozen state. The root cause is that interactivePopGestureRecognizer is still active even when the back button is not visible.

To prevent this, disable the recognizer whenever you hide the back button:

swift
1override func viewWillAppear(_ animated: Bool) {
2    super.viewWillAppear(animated)
3    navigationItem.hidesBackButton = true
4    navigationController?.interactivePopGestureRecognizer?.isEnabled = false
5}

Custom Transitions

If your navigation controller uses custom transition animations, the interactive pop gesture can conflict with the transition. In these cases, either disable the gesture entirely or implement UIViewControllerInteractiveTransitioning to provide a custom interactive transition that works with your animation.

swift
1func navigationController(
2    _ navigationController: UINavigationController,
3    interactionControllerFor animationController: UIViewControllerAnimatedTransitioning
4) -> UIViewControllerInteractiveTransitioning? {
5    // Return your custom interaction controller, or nil to disable interaction
6    return nil
7}

Valid Reasons to Disable the Back Swipe

Before disabling this gesture, consider whether it is truly necessary. The back swipe is a core iOS navigation pattern that users expect. Removing it without good reason makes the app feel broken.

ReasonAlternative to Consider
Preventing loss of unsaved form dataShow a confirmation alert instead
Multi-step wizard flowAllow back but show "discard changes?" prompt
Signature or drawing canvasDisable only during active drawing
Full-screen media playerUse a modal presentation instead of push
Payment processing in progressDisable during the network request only

In many cases, a confirmation dialog is better UX than fully disabling navigation. The user keeps their sense of control, and accidental data loss is still prevented.

Common Pitfalls

Disabling the gesture in viewDidLoad without a corresponding restore is the most common bug. It affects every view controller pushed after yours, and the symptom (broken back swipe on unrelated screens) makes the cause hard to trace.

Setting the gesture recognizer delegate without restoring it in viewWillDisappear creates dangling references. When the navigation controller tries to call delegate methods on a deallocated object, the app crashes.

Disabling the back swipe without providing any alternative escape path (no back button, no cancel button, no close gesture) traps the user on the screen. Always provide an obvious way to navigate away.

Hiding the back button but leaving interactivePopGestureRecognizer enabled causes the frozen navigation bug described above. Always pair hidesBackButton = true with gesture disable.

Testing only the happy path (pushing and popping cleanly) misses the case where the user starts a swipe, cancels it, then tries to navigate again. Test partial swipe cancellation on every screen where you modify the gesture recognizer behavior.

Summary

  • Disable the back swipe by setting interactivePopGestureRecognizer?.isEnabled = false in viewWillAppear.
  • Always restore it to true in viewWillDisappear to prevent leaking the policy to other screens.
  • Use a UIGestureRecognizerDelegate for conditional control (e.g., only block when there are unsaved changes).
  • Centralize gesture policies in a custom UINavigationController subclass when many screens need different rules.
  • Always provide an alternative navigation path (back button, cancel, close) when disabling the swipe gesture.
  • Test partial swipe cancellation to catch the frozen navigation bug.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.