335

In iOS 7 Apple added a new default navigation behavior. You can swipe from the left edge of the screen to go back on the navigation stack. But in my app, this behavior conflicts with my custom left menu. So, is it possible to disable this new gesture in UINavigationController?

hfossli
  • 21,612
  • 9
  • 111
  • 127
ArtFeel
  • 11,322
  • 4
  • 26
  • 41
  • 1
    [*How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem?*](http://stackoverflow.com/questions/34942571/how-to-enable-back-left-swipe-gesture-in-uinavigationcontroller-after-setting-le) – Itachi Jan 22 '16 at 09:12
  • 2
    I also found out that if you set `navigationItem.hidesBackButton = true`, this gesture also gets disabled. In my case i implemented a custom back button and add as a `leftBarButtonItem` – Umair Mar 10 '16 at 11:30

17 Answers17

602

I found a solution:

Objective-C:

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

Swift 3+:
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false

ArtFeel
  • 11,322
  • 4
  • 26
  • 41
  • This causes an invalid argument exception when run on iOS6. – Ant Oct 08 '13 at 08:29
  • 29
    Of course, you need to check the availability of new methods if you are supporting old versions of the iOS. – ArtFeel Oct 08 '13 at 09:07
  • 2
    Is there a way to disable it for a potion of the view? – Marc Nov 23 '13 at 17:28
  • 11
    You can `enable / disable` recognizer on `viewDidAppear:` / `viewDidDisappear`. Or, you can implement `UIGestureRecognizerDelegate` protocol with your more complex logic and set it as `recognizer.delegate` property. – ArtFeel Nov 25 '13 at 12:25
  • I can confirm that this still works for me on iOS 8. – Myxtic Dec 08 '14 at 04:25
  • 26
    On iOS8, setting `self.navigationController.interactivePopGestureRecognizer.enabled` property does not work in following view's methods: `viewDidLoad`, `viewWillAppear`, `viewDidAppear`, `viewDidDisappear`, but works in method `viewWillDisappear`. On iOS7 it works in all of above mentioned methods. So try to use it in any other methods while working on the viewController, I confirm it works for me on iOS8 when I click on some button inside of the view. – Sihad Begovic Dec 29 '14 at 08:29
  • Those having issues with iOS 8 have a look at my updated answer below. This worked for me - hopefully it will work for you too. – Charlie Seligman Feb 01 '15 at 17:51
  • Yer stick it in ViewWillLayoutSubviews - although I guess it will get called multiple times... – Rambatino Feb 04 '15 at 09:43
  • 8
    Can confirm that this will not work in iOS8 in viewDidLoad and viewWillAppear, putting it into viewwilllayoutgubviews did the trick – tonytastic Feb 18 '15 at 15:06
  • 1
    If it's for `DetailViewController` of `UISplitViewController` like mine, use `self.navigationController.navigationController.interactivePopGestureRecognizer.enabled = NO;` – SFF Aug 18 '15 at 21:46
  • Anyone know if there's a plugin for phonegap to accomplish this? – Seth Caldwell Nov 13 '15 at 19:40
  • I also found out that if you set `navigationItem.hidesBackButton = true`, this gesture also gets disabled. In my case i implemented a custom back button and add as a `leftBarButtonItem` – Umair Mar 10 '16 at 11:30
  • This does not work in iOS9. The below answer from Antoine (also setting the delegate) does work. Its a bit confusing, because if you put it in e.g. viewDidAppear it seams to work sometimes, but this actually happens because the first time you edge-swipe it brings up the underlying vc, and then calls the viewDidAppear again after which edge-swipe is indeed disabled. However its not disabled when the screen is initially presented! – HixField May 17 '16 at 07:41
50

I found out setting the gesture to disabled only doesn't always work. It does work, but for me it only did after I once used the backgesture. Second time it wouldn't trigger the backgesture.

Fix for me was to delegate the gesture and implement the shouldbegin method to return NO:

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

    // Disable iOS 7 back gesture
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        self.navigationController.interactivePopGestureRecognizer.delegate = self;
    }
}

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

    // Enable iOS 7 back gesture
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = YES;
        self.navigationController.interactivePopGestureRecognizer.delegate = nil;
    }
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    return NO;
}
Antoine
  • 21,544
  • 11
  • 81
  • 91
  • 1
    Thanks! This is required to fully disable back swipe. It still exists in iOS 8, and smells like an Apple bug. – Eric Chen Oct 14 '14 at 08:23
  • I dont know why but a view controller in my app for some unknown reason was crashing on this back gesture.. this saved me from finding it as I didnt needed this back gesture and so i disabled using this code.. +1 – Ahsan Ebrahim May 08 '15 at 12:27
  • 1
    @AhsanEbrahim, when the back gesture starts, `viewWillAppear` is called on the view behind the current view. This can cause havoc in code logic since the current view is still active. Might be the cause of your crash. – phatmann May 31 '15 at 12:06
  • Are the `enabled` yes/no lines needed? You return `NO` from `gestureRecognizerShouldBegin`, isn't that sufficient? – ToolmakerSteve Sep 07 '16 at 11:35
  • For anyone who was stuck on this like I was, if you are doing this for a master-detail view in a split view controller, you have to do the same for `self.navigationController.navigationController`. See https://stackoverflow.com/a/50012503/5605365 – Matt Apr 25 '18 at 17:45
  • The second line of code in the if statement, the one that assigns self as the delegate, generates a build error for me: Assigning to 'id _Nullable' from incompatible type 'VCPrivateData *const __strong'. But commenting that line out and leaving the first line disabled things fine. I did not need to implement gestureRecognizerShouldBegin either. – Alyoshak May 01 '18 at 21:49
  • [setting the delegate to nil leads to hanging issues](https://stackoverflow.com/a/27600715/375300) when you go back to the root view controller and make a swipe gesture before navigating elsewhere. – albertamg Jul 19 '19 at 13:58
30

Just remove gesture recognizer from NavigationController. Work in iOS 8.

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    [self.navigationController.view removeGestureRecognizer:self.navigationController.interactivePopGestureRecognizer];
  • 7
    Also works in iOS 10, this should be the accepted answer. By the way, if you want to re-enable it, do `[self.navigationController.view addGestureRecognizer:self.navigationController.interactivePopGestureRecognizer]` somewhere. – ooops Oct 21 '16 at 15:49
23

As of iOS 8 the accepted answer no longer works. I needed to stop the swipping to dismiss gesture on my main game screen so implemented this:

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

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.delegate = self;
    }
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.delegate = nil;
    }

}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
     return NO;
}
Charlie Seligman
  • 3,508
  • 4
  • 50
  • 84
  • 2
    While this works with iOS8 I get a warning on the line *.delegate = self; stating: Assigning to id' from incompatible type 'ViewController *const __strong' – David Douglas Feb 06 '15 at 19:03
  • 2
    As of iOS8, the accepted answer still works as expected. You are probably doing something else wrong.. – Alexandre G Feb 18 '15 at 09:11
  • Managed to get it semi working by calling the accepted answer in viewWillLayoutSubviews. However, swiping did cause the page to call 'viewDidLoad' again so reverted back to my answer above – Charlie Seligman Feb 19 '15 at 21:15
  • @DavidDouglas: perhaps you could eliminate the warning with this code: __weak __typeof(self) theSafeSelf = self? Then set the delegate to theSafeSelf. – lifjoy Mar 13 '15 at 20:45
  • 1
    @DavidDouglas: You need to add to the interface to get rid of that warning – primehalo Aug 19 '16 at 04:11
  • As per Jack's answer, may be safer (correct in more situations), instead of clearing with `nil` in `viewWillDisappear`, to save the previous value in `viewDidAppear`,then restore to that saved value. – ToolmakerSteve Sep 07 '16 at 11:40
  • [setting the delegate to nil leads to hanging issues](https://stackoverflow.com/a/27600715/375300) when you go back to the root view controller and make a swipe gesture before navigating elsewhere. – albertamg Jul 19 '19 at 13:56
20

I've refined Twan's answer a bit, because:

  1. your view controller may be set as a delegate to other gesture recognisers
  2. setting the delegate to nil leads to hanging issues when you go back to the root view controller and make a swipe gesture before navigating elsewhere.

The following example assumes iOS 7:

{
    id savedGestureRecognizerDelegate;
}

- (void)viewWillAppear:(BOOL)animated
{
    savedGestureRecognizerDelegate = self.navigationController.interactivePopGestureRecognizer.delegate;
    self.navigationController.interactivePopGestureRecognizer.delegate = self;
}

- (void)viewWillDisappear:(BOOL)animated
{
    self.navigationController.interactivePopGestureRecognizer.delegate = savedGestureRecognizerDelegate;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if (gestureRecognizer == self.navigationController.interactivePopGestureRecognizer) {
        return NO;
    }
    // add whatever logic you would otherwise have
    return YES;
}
Ja͢ck
  • 161,074
  • 33
  • 239
  • 294
  • +1 _"setting the delegate to nil leads to hanging issues when you go back to the root view controller and make a swipe gesture before navigating elsewhere."_ – albertamg Jul 19 '19 at 13:55
10

For Swift:

navigationController!.interactivePopGestureRecognizer!.enabled = false
Elijah
  • 7,223
  • 2
  • 49
  • 49
iPhone 7
  • 1,483
  • 20
  • 54
  • 12
    This works, though I'd suggest using optional chaining instead of force unwrapping. e.g. self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false – Womble Mar 03 '17 at 01:34
10

Please set this in root vc:

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:YES];
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;

}

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:YES];
    self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
reza_khalafi
  • 5,427
  • 4
  • 44
  • 70
6

swift 5, swift 4.2 can use the code in the below.

// disable
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
// enable
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
Zgpeace
  • 1,989
  • 15
  • 20
5

EDIT

If you want to manage swipe back feature for specific navigation controllers, consider using SwipeBack.

With this, you can set navigationController.swipeBackEnabled = NO.

For example:

#import <SwipeBack/SwipeBack.h>

- (void)viewWillAppear:(BOOL)animated
{
    navigationController.swipeBackEnabled = NO;
}

It can be installed via CocoaPods.

pod 'SwipeBack', '~> 1.0'

I appologize for lack of explanation.

Elijah
  • 7,223
  • 2
  • 49
  • 49
devxoul
  • 1,908
  • 1
  • 15
  • 13
  • 6
    When promoting a project you're involved with you must disclose your affiliation with it. –  Feb 07 '15 at 08:34
  • 2
    Moreover, your project's only purpose is to manually enable the swipe gesture when the default system one isn't working, whereas the question asks how to disable that system wide gesture, so even if you set `self.navigationController.swipeBackEnabled = NO` I'm pretty sure this will only disable your library's swipe back gesture but the system's one will still be enabled. –  Feb 07 '15 at 08:37
  • 1
    Sorry for my short answer, I've just edited my answer with additional information: "useful for specific navigation controllers". Thanks! – devxoul Feb 28 '15 at 17:20
  • It appears to use swizzle which is no longer allowed. iOS8? – Matt May 18 '15 at 01:33
  • @Matt, Sorry, I couldn't understand what you mean. Could you please explain me "~ no longer allowed. iOS 8?" means? Thanks! – devxoul May 18 '15 at 06:07
  • @André, SwipeBack plays with native(system) gesture recognizer. You can manage native swipeback gesture enabled with it :) – devxoul May 18 '15 at 06:10
  • 1
    @devxoul I am sorry! I thought I had read something a while ago saying that swizzling was no longer allowed. However, I cannot find anything that says this. Guess I am wrong. – Matt May 19 '15 at 08:39
  • I used this lib and it comes with the same some bugs. The trick is to do like FB is doing! – João Nunes Feb 22 '16 at 09:00
5

it works for me in ios 10 and later :

- (void)viewWillAppear:(BOOL)animated {
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }

}

it doesnt work on viewDidLoad() method.

Logic
  • 789
  • 9
  • 24
4

My method. One gesture recognizer to rule them all:

class DisabledGestureViewController: UIViewController: UIGestureRecognizerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController!.interactivePopGestureRecognizer!.delegate = self
    }

    func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
        // Prevent going back to the previous view
        return !(navigationController!.topViewController is DisabledGestureViewController)
    }
}

Important: don't reset the delegate anywhere in the navigation stack: navigationController!.interactivePopGestureRecognizer!.delegate = nil

SoftDesigner
  • 5,089
  • 2
  • 45
  • 45
4

This is the way on Swift 3

works for me

    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
Tayo119
  • 173
  • 1
  • 11
3

All of these solutions manipulate Apple's gesture recognizer in a way they do not recommend. I've just been told by a friend that there's a better solution:

[navigationController.interactivePopGestureRecognizer requireGestureRecognizerToFail: myPanGestureRecognizer];

where myPanGestureRecognizer is the gesture recognizer you are using to e.g. show your menu. That way, Apple's gesture recognizer doesn't get turned back on by them when you push a new navigation controller and you don't need to rely on hacky delays that may fire too early if your phone is put to sleep or under heavy load.

Leaving this here because I know I'll not remember this the next time I need it, and then I'll have the solution to the issue here.

uliwitness
  • 7,806
  • 31
  • 50
2

This works in viewDidLoad: for iOS 8:

  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      self.navigationController.interactivePopGestureRecognizer.enabled = false;
  });

Lots of the problems could be solved with help of the good ol' dispatch_after.

Though please note that this solution is potentially unsafe, please use your own reasoning.

Update

For iOS 8.1 delay time should be 0.5 seconds

On iOS 9.3 no delay needed anymore, it works just by placing this in your viewDidLoad:
(TBD if works on iOS 9.0-9.3)

navigationController?.interactivePopGestureRecognizer?.enabled = false
Dannie P
  • 4,096
  • 3
  • 26
  • 46
  • Unless you know when the gesture recognizer is installed on the view, waiting an arbitrary amount of time to disable it may or may not work. – kalperin Jan 31 '16 at 20:04
  • @kalperin it's not guaranteed to work, though it's a very handy solution at some times. Use your own reasoning. – Dannie P Feb 06 '16 at 20:57
  • It working for me having version greater than iOS 8.1 :) – iChirag Feb 16 '16 at 13:53
  • `viewDidLoad` plus delay is a risky programming practice. A bad habit to start. What if user starts the swipe before your delayed call kicks in? There is no safe time that is guaranteed to be long enough yet not too long. That is why other answers, posted long before yours, suggest placing the code in `viewDidAppear`. That ensures that everything is installed. Don't invent arbitrary delays; use Apple's sequence of calls as intended. – ToolmakerSteve Sep 07 '16 at 11:50
  • 1
    @iChirag true. I've noted that for 8.1 you need 0.5 second delay – Dannie P Sep 12 '16 at 12:38
  • @ToolmakerSteve the whole point of using this delay is that it does not work via iOS 8 in callbacks like `viewDidAppear`, which looks like a UIKit bug. This [gif](https://www.dropbox.com/s/bu13q7ibqmfs6g7/disabling-backswipe.gif?dl=0) demonstrates the issue. Sure one should use `viewWillAppear` when it works. When one sees a delay like this one knows it's potentially unsafe, so own reasoning should be applied. Nevertheless it is a nice and quick workaround on iOS 8. – Dannie P Sep 12 '16 at 12:49
  • UIKit engineers specified in docs this `You can use this property to retrieve the gesture recognizer and tie it to the behavior of other gesture recognizers in your user interface`. This tells nothing about disabling this gesture, so technically it's not a bug, rather not yet implemented feature request. – Dannie P Sep 12 '16 at 13:00
2

None of the given answers helped me to resolve the issue. Posting my answer here; may be helpful for someone

Declare private var popGesture: UIGestureRecognizer? as global variable in your viewcontroller. Then implement the code in viewDidAppear and viewWillDisappear methods

override func viewDidAppear(animated: Bool) {

    super.viewDidAppear(animated)

    if self.navigationController!.respondsToSelector(Selector("interactivePopGestureRecognizer")) {

        self.popGesture = navigationController!.interactivePopGestureRecognizer
        self.navigationController!.view.removeGestureRecognizer(navigationController!.interactivePopGestureRecognizer!)
    }
}


override func viewWillDisappear(animated: Bool) {

    super.viewWillDisappear(animated)

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

This will disable swipe back in iOS v8.x onwards

Augustine P A
  • 4,787
  • 2
  • 30
  • 38
  • I am trying to imagine under what circumstances this would work, but Jack's would not. You say you tried all of the other answers: what went wrong when you tried Jack's? – ToolmakerSteve Sep 07 '16 at 11:56
  • On the other hand, this does seem simpler than Jack's, so maybe its not important. Decided I like this, because don't have to declare my class as a delegate, nor manipulate `interactivePopGestureRecognizer.delegate`. – ToolmakerSteve Sep 07 '16 at 12:04
  • BTW, code can be simplified. Remove `if( .. respondsToSelector ..`. The next line sets popGesture to a recognizer, or to nil. Then use its value: `if (self.popGesture != nil) self.navigationController .. removeGestureRecognizer( self.popGesture )`. – ToolmakerSteve Sep 07 '16 at 12:22
1

For Swift 4 this works:

class MyViewController: UIViewController, UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationController?.interactivePopGestureRecognizer?.gesture.delegate = self
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)

        self.navigationController?.interactivePopGestureRecognizer?.gesture.isEnabled = false
    }

}
Lokesh SN
  • 1,051
  • 5
  • 18
Mat0
  • 1,095
  • 11
  • 27
  • You should not override the interactive pop gesture delegate as it will cause undocumented behavior – Josh Bernfeld Feb 13 '18 at 02:18
  • I think it's not really overriding the delegate but just modifying the Boolean variable that they've provided for this very purpose, so it won't be a problem – Lokesh SN Dec 06 '18 at 07:41
1

It worked for me for most of the viewcontrollers.

self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false

It wasn't not working for some viewcontrollers like UIPageViewController. On UIPageViewController's pagecontentviewcontroller below code worked for me.

override func viewDidLoad() {
   self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
   self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
override func viewWillDisappear(_ animated: Bool) {
   self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
   self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
}

On UIGestureRecognizerDelegate,

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
   if gestureRecognizer == self.navigationController?.interactivePopGestureRecognizer {
      return false
}
      return true
}
Faris Muhammed
  • 577
  • 7
  • 13