72
votes

Been looking around on here for a while but can't seem to find a working solution.

I'm trying to disable the swipe to go back to previous view gesture, in Swift.

I've tried a variety of solutions including:

self.navigationController?.interactivePopGestureRecognizer.enabled = false

and

self.navigationController.interactivePopGestureRecognizer.delegate = self

func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer!) -> Bool {
    return false
}

Is there a new method of doing this or some other method that works?

14
It's causing an issue when a user doesn't fully swipe, and interferes with the nature of the UI we are using (seeking time in an audio track)Phil Hudson
Well, I don't want to derail your question, but I suggest you (1) resolve the doesn't-fully-swipe issue and (2) indent your audio track seeking UI so it's not so close to the edge. This is expected UX, and is especially important for iPhone 6 Plus and iPad users where it's kind of a hassle to get to the navigation bar.Aaron Brager
Anyway, your code should disable the gesture recognizer. Does it work on older versions of iOS?Aaron Brager
Ah good point about iPhone 6 - maybe I'll look at modifying the dealloc method - but I'll still leave the question open as I'm interested nonethelessPhil Hudson
I'm testing on iOS 8 as the minimumPhil Hudson

14 Answers

157
votes

The following is an easy approach to disabling & re-enabling the swipe back.

Swift 3.x & up

In a viewDidLoad/willAppear/didAppear method add:

navigationController?.interactivePopGestureRecognizer?.isEnabled = false

Just keep in mind that if you do it with viewDidLoad, then the next time you open the view, it may not be set depending upon whether or not it remains in your stack.

Unless you want it to remain off, you will need to turn it back on when the view is closed via either willMove(toParentViewController:) or willDisappear. Your navigationController will be nil at viewDidDisappear, so that is too late.

navigationController?.interactivePopGestureRecognizer?.isEnabled = true

A special note on SplitViewControllers:

As pointed out by CompC in the comments, you will need to call the second navigation controller to apply it to a detail view as such:

navigationController?.navigationController?.interactivePopGe‌​stureRecognizer?.isE‌​nabled = false

Swift 2.2 & Objective-C

Swift versions 2.x & below:

navigationController?.interactivePopGestureRecognizer?.enabled

Objective-C:

self.navigationController.interactivePopGestureRecognizer.enabled
20
votes

You could disable it but that would not be to recommended as most iOS users go back by swiping and less by pressing the back button. If you want to disable it it would be more reasonable to use a modal segue instead of a push segue which is not that big of a transfer. If you really want to get rid of the swipe to go back function I would just disable the back button and have a done button on the top right of the screen.

self.navigationController?.navigationItem.backBarButtonItem?.isEnabled = false;
19
votes

I was able to do this by returning false in gestureRecognizerShouldBegin

class ViewController2: UIViewController, UIGestureRecognizerDelegate {
...
override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    self.navigationController?.interactivePopGestureRecognizer.delegate = self
}

func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
    return false
}
12
votes

Add this line before pushing view controller to navigation controller

self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
6
votes

Nothing wrong with either answer from Hari or Stefan but this is more succinct. Just put it in viewDidLoad and you're done.

if navigationController!.respondsToSelector(Selector("interactivePopGestureRecognizer")) {
    navigationController!.view.removeGestureRecognizer(navigationController!.interactivePopGestureRecognizer)
}

EDIT:

One small caveat is that if the Navigation Controller was opened by another view and the Navigation Controller is closed then you'll get an EXC_BAD_ACCESS error. To fix it you have to save the original UIGestureRecognizer and put it back when you exit the view.

Declare:

private var popGesture: UIGestureRecognizer?

Immediately before removing the gesture:

popGesture = navigationController!.interactivePopGestureRecognizer

Then when closing the view:

If popGesture != nil {
    navigationController!.view.addGestureRecognizer(popGesture!)
}
3
votes

for objective -c

-(void)viewWillAppear:(BOOL)animated{
  [super viewWillAppear:true];

  self.navigationController.interactivePopGestureRecognizer.enabled = NO;

}
3
votes

Instead of

self.navigationController.pushViewController(VC, animated: Bool)

call

self.navigationController.setViewContollers([VC], animated: Bool)

setViewControllers replaces the all the VCs on the stack, instead of adding a new controller on top. This means that the new set VC is the root VC, and the user cannot go back.

This is most effective when you only want to disable the swipe on a single VC, and keep the swipe-to-back for the other VC.

If you want users to be able to go back, just not through swiping, do not use this method as it will disable all backs (as there is no VC to go back to).

2
votes

I generally make sure that swipe back is enabled in as many places as possible, even adding a custom gesture recognizer to add it to modal screens. However for an authentication and download process in my app I start the process with a modal navigation controller and then push the view for each next step. However, once it's completed I want to prevent them from backing up into the authentication screens.

For this scenario I've been using:

navigationController?.interactivePopGestureRecognizer?.isEnabled = false
navigationItem.hidesBackButton = true

in viewWillAppear() on the final screen. You can undo these in viewWillDisappear() if you're pushing another view and need them there.

2
votes

RowanPD's logic for Swift 4

private var popGesture: UIGestureRecognizer?

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if navigationController!.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
        self.popGesture = navigationController!.interactivePopGestureRecognizer
        self.navigationController!.view.removeGestureRecognizer(navigationController!.interactivePopGestureRecognizer!)
    }

}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if let gesture = self.popGesture {
        self.navigationController!.view.addGestureRecognizer(gesture)
    }

}
1
votes

This is something you missed if it doesn't work after you tried all.

  1. Add navigationController?.interactivePopGestureRecognizer?.isEnabled = false to your viewWillAppear(animated:) method.
  2. if it doesn't work, remove navigation delegate from the view controller. Check again if your view controller is confirming UINavigationControllerDelegate, UIGestureRecognizerDelegate protocols. if so, just remove it.
0
votes

If requirement is to show side menu on some of the screens then add AddScreenEdgePanGesture on this specific view instead of navigationController view

replace it

SideMenuManager.default.menuAddScreenEdgePanGesturesToPresent(toView: self.navigationController?.view)

with this

SideMenuManager.default.menuAddScreenEdgePanGesturesToPresent(toView: self.view)
0
votes

If you don't care about system back button appearance (for example, if you're using custom back button or navigation bar is hidden at all), it might help you:

navigationItem.hidesBackButton = true

It hides back button and disables swipe back gesture.

0
votes

Only complete removal of the gesture recognizer worked for me (from the presenting view controller).

if let navigationController = parent.navigationController,
   let interactivePopGestureRecognizer = navigationController.interactivePopGestureRecognizer {
    navigationController.view.removeGestureRecognizer(interactivePopGestureRecognizer)
}
0
votes

Don't Use this if you don't want to come back, or you set the new rootViewController.

self.navigationController.pushViewController(VC, animated: Bool)

Use this

self.navigationController.setViewContollers([VC], animated: Bool)

setViewControllers Remove all the View Controllers on the stack then the user cannot go back. it will disable all backs