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?
18 Answers
I found a solution:
Objective-C:
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
Swift 3+:
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
-
29Of course, you need to check the availability of new methods if you are supporting old versions of the iOS.– ArtFeelCommented Oct 8, 2013 at 9:07
-
2
-
11You can
enable / disable
recognizer onviewDidAppear:
/viewDidDisappear
. Or, you can implementUIGestureRecognizerDelegate
protocol with your more complex logic and set it asrecognizer.delegate
property.– ArtFeelCommented Nov 25, 2013 at 12:25 -
26On iOS8, setting
self.navigationController.interactivePopGestureRecognizer.enabled
property does not work in following view's methods:viewDidLoad
,viewWillAppear
,viewDidAppear
,viewDidDisappear
, but works in methodviewWillDisappear
. 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. Commented Dec 29, 2014 at 8:29 -
8Can confirm that this will not work in iOS8 in viewDidLoad and viewWillAppear, putting it into viewwilllayoutgubviews did the trick Commented Feb 18, 2015 at 15:06
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;
}
-
1Thanks! This is required to fully disable back swipe. It still exists in iOS 8, and smells like an Apple bug. Commented Oct 14, 2014 at 8: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 Commented May 8, 2015 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.– phatmannCommented May 31, 2015 at 12:06 -
Are the
enabled
yes/no lines needed? You returnNO
fromgestureRecognizerShouldBegin
, isn't that sufficient? Commented Sep 7, 2016 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 stackoverflow.com/a/50012503/5605365– MattCommented Apr 25, 2018 at 17:45
Just remove gesture recognizer from NavigationController. Work in iOS 8.
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
[self.navigationController.view removeGestureRecognizer:self.navigationController.interactivePopGestureRecognizer];
-
8Also 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.– YamCommented Oct 21, 2016 at 15:49
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;
}
-
2While this works with iOS8 I get a warning on the line *.delegate = self; stating: Assigning to id<UIGestureRecognizerDelegate>' from incompatible type 'ViewController *const __strong' Commented Feb 6, 2015 at 19:03
-
2As of iOS8, the accepted answer still works as expected. You are probably doing something else wrong.. Commented Feb 18, 2015 at 9: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 Commented Feb 19, 2015 at 21:15
-
@DavidDouglas: perhaps you could eliminate the warning with this code: __weak __typeof(self) theSafeSelf = self? Then set the delegate to theSafeSelf.– lifjoyCommented Mar 13, 2015 at 20:45
-
1@DavidDouglas: You need to add <UIGestureRecognizerDelegate> to the interface to get rid of that warning Commented Aug 19, 2016 at 4:11
I've refined Twan's answer a bit, because:
- your view controller may be set as a delegate to other gesture recognisers
- 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;
}
-
+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." Commented Jul 19, 2019 at 13:55
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;
}
For Swift:
navigationController!.interactivePopGestureRecognizer!.enabled = false
-
16This works, though I'd suggest using optional chaining instead of force unwrapping. e.g. self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false– WombleCommented Mar 3, 2017 at 1:34
swift 5, swift 4.2 can use the code in the below.
// disable
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
// enable
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
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.
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.
-
6When promoting a project you're involved with you must disclose your affiliation with it.– user2629998Commented Feb 7, 2015 at 8:34
-
2Moreover, 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.– user2629998Commented Feb 7, 2015 at 8:37 -
1Sorry for my short answer, I've just edited my answer with additional information: "useful for specific navigation controllers". Thanks!– devxoulCommented Feb 28, 2015 at 17:20
-
-
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.– MattCommented May 19, 2015 at 8:39
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
This is the way on Swift 3
works for me
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
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.
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
-
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? Commented Sep 7, 2016 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
. Commented Sep 7, 2016 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 )
. Commented Sep 7, 2016 at 12:22
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
-
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.– kalperinCommented Jan 31, 2016 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 PCommented Feb 6, 2016 at 20:57
-
It working for me having version greater than iOS 8.1 :) Commented Feb 16, 2016 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 inviewDidAppear
. That ensures that everything is installed. Don't invent arbitrary delays; use Apple's sequence of calls as intended. Commented Sep 7, 2016 at 11:50 -
1@iChirag true. I've noted that for 8.1 you need 0.5 second delay– Dannie PCommented Sep 12, 2016 at 12:38
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
}
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
}
}
-
You should not override the interactive pop gesture delegate as it will cause undocumented behavior Commented Feb 13, 2018 at 2: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 Commented Dec 6, 2018 at 7:41
self.navigationController.pushViewController(VC, animated: Bool)
call
self.navigationController.setViewContollers([VC], animated: Bool)
setViewControllers replaces 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)
navigationItem.hidesBackButton = true
, this gesture also gets disabled. In my case i implemented a custom back button and add as aleftBarButtonItem