82

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?

CodeBender
  • 35,668
  • 12
  • 125
  • 132
Phil Hudson
  • 3,819
  • 8
  • 35
  • 59
  • 1
    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 Jul 30 '15 at 18:55
  • 1
    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 Jul 30 '15 at 18:57
  • Anyway, your code should disable the gesture recognizer. Does it work on older versions of iOS? – Aaron Brager Jul 30 '15 at 18:58
  • 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 nonetheless – Phil Hudson Jul 30 '15 at 18:58
  • I'm testing on iOS 8 as the minimum – Phil Hudson Jul 30 '15 at 19:00
  • I was able to do it with returning false in gestureRecognizerShouldBegin. Are you getting callback at gestureRecognizerShouldBegin ? Maybe you put this code in wrong view controller... – Hari Kunwar Jul 30 '15 at 19:16

16 Answers16

184

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
CodeBender
  • 35,668
  • 12
  • 125
  • 132
  • 2
    I was having a bit of trouble getting this to work, until I realized that since the view controller I was trying to disable this for was on the detail side of the Split View Controller, it's technically in a separate navigation controller (even though, when collapsed, it looks like it's in the same controller). To get around this I had to do: `navigationController?.navigationController?.interactivePopGestureRecognizer?.isEnabled = false` – CompC Nov 12 '16 at 06:16
  • @CompC Thanks for the update, never had that scenario, so good to know. – CodeBender Nov 12 '16 at 16:17
  • it is sufficient to disable it in didAppear, and re-enable it on willDisappear. Especially re-enable it on didDisappear is useless, because navigationController property is nil already. – Vilém Kurz Dec 16 '16 at 08:34
  • 3
    Thanks this should be the Correct Answer – Hernan Arber Apr 23 '17 at 09:30
  • Is it possible to disable this on through out app? like code in AppDelegate etc.? – iAj Jan 20 '18 at 04:25
  • 1
    @iajmeri43 Yes, create a subclass of UINavigationController, and then set the property in its viewDidLoad. As long as the nav controller is utilized by all the views you want within the app, it will work. – CodeBender Jan 20 '18 at 21:48
  • Thought I'd point out that interactivePopGestureRecorgizer may not be initialized depending on when/where you try to set navigationController?.interactivePopGestureRecognizer?.isEnabled = false. I'm using coordinators and this was nil after I initialized the NavController and before I pushed the first ViewController. – TMin Feb 11 '19 at 22:16
  • Thanks @TMin, I've used this when initializing navigation controllers without incident, but it is useful to see spots where it slips through. – CodeBender Feb 11 '19 at 22:20
23

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
}
Hari Kunwar
  • 1,661
  • 14
  • 10
21

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;
Stefan DeClerck
  • 1,162
  • 2
  • 12
  • 22
18

Add this line before pushing view controller to navigation controller

self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
7

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!)
}
RowanPD
  • 374
  • 4
  • 14
5

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).

Iskeraet
  • 731
  • 1
  • 6
  • 12
4

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)
    }

}
David Seek
  • 16,783
  • 19
  • 105
  • 136
3

for objective -c

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

  self.navigationController.interactivePopGestureRecognizer.enabled = NO;

}
rfornal
  • 5,072
  • 5
  • 30
  • 42
Maulik Patel
  • 2,045
  • 17
  • 24
3

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.
Li Jin
  • 1,879
  • 2
  • 16
  • 23
2

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.

blwinters
  • 1,911
  • 19
  • 40
1

Come here a little bit late. In my case self.navigationController?.navigationItem.backBarButtonItem?.isEnabled = false; not working. So I do this: you can present view controller instead of push view controller. This way the swipe back gesture will not apply to the view controller.

navigationController?.present(vc, animated: true)

You could use dismiss for your custom back button

self.dismiss(animated: true)

Note: You could set VC modal presentation style before present it to make sure it's full screen.

vc.modalPresentationStyle = .fullScreen

Hope this help.

KaMaHe
  • 413
  • 1
  • 7
  • 18
0

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)
Waseem Sarwar
  • 2,645
  • 1
  • 21
  • 18
0

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)
}
Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
0

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

Muhammad Ahmad
  • 388
  • 4
  • 9
0

this worked for me

gesture(DragGesture(minimumDistance: 10, coordinateSpace: .global))

so minimum distance is the distance to which drag gesture start listening, setting to 0 removes any listening, but it will remove all interactions be aware, i have changed 0 to 10 to listen to tap gestures, but in your screen if you have any other interaction it will not work after adding this,

JSONParser
  • 1,112
  • 2
  • 15
  • 30
-4

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.