No Swipe Back when hiding navigation bar in UINavigationController

I really like the swipe set inherited from embedding your views in the UINavigationController . Unfortunately, I cannot find a way to hide the NavigationBar , but I still have a touch pan back gesture . I can write my own gestures, but I prefer not to, but instead rely on the UINavigationController back gesture .

if I uncheck the storyboard, the kickback will not work

enter image description here

alternatively, if I programmatically hide this, the same scenario.

 - (void)viewDidLoad { [super viewDidLoad]; [self.navigationController setNavigationBarHidden:YES animated:NO]; // and animated:YES } 

Is there no way to hide the top of the NavigationBar and still have a punch?

+69
ios objective-c xcode uigesturerecognizer uinavigationcontroller
Jul 12 '14 at 6:21
source share
17 answers

The handler that works must set the interactivePopGestureRecognizer delegate from UINavigationController to nil as follows:

 [self.navigationController.interactivePopGestureRecognizer setDelegate:nil]; 

But in some situations, this can create strange effects.

+89
Jul 17 '14 at 15:05
source share

Problems with other methods

Setting interactivePopGestureRecognizer.delegate = nil has unintended side effects.

Setting navigationController?.navigationBar.hidden = true works, but does not allow you to hide the change in the navigation bar.

Finally, it is best to create a model object that is a UIGestureRecognizerDelegate for your navigation controller. Installing it on the controller in the UINavigationController stack causes EXC_BAD_ACCESS errors.

Complete solution

First add this class to your project:

 class InteractivePopRecognizer: NSObject, UIGestureRecognizerDelegate { var navigationController: UINavigationController init(controller: UINavigationController) { self.navigationController = controller } func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return navigationController.viewControllers.count > 1 } // This is necessary because without it, subviews of your top controller can // cancel out your gesture recognizer on the edge. func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } } 

Then install your interactivePopGestureRecognizer.delegate navigation controller in an instance of your new InteractivePopRecognizer class.

 var popRecognizer: InteractivePopRecognizer? override func viewDidLoad() { super.viewDidLoad() setInteractiveRecognizer() } private func setInteractiveRecognizer() { guard let controller = navigationController else { return } popRecognizer = InteractivePopRecognizer(controller: controller) controller.interactivePopGestureRecognizer?.delegate = popRecognizer } 

Enjoy the hidden navigation bar without any side effects that works even if your top controller has a table, collection, or scroll view.

+59
Dec 20 '16 at 18:09
source share

In my case, to prevent strange effects

Root view controller

 override func viewDidLoad() { super.viewDidLoad() // Enable swipe back when no navigation bar navigationController?.interactivePopGestureRecognizer?.delegate = self } func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if(navigationController!.viewControllers.count > 1){ return true } return false } 

http://www.gampood.com/pop-viewcontroller-with-out-navigation-bar/

+52
Mar 07 '15 at 20:00
source share

You can subclass UINavigationController as follows:

 @interface CustomNavigationController : UINavigationController<UIGestureRecognizerDelegate> @end 

Implementation:

 @implementation CustomNavigationController - (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated { [super setNavigationBarHidden:hidden animated:animated]; self.interactivePopGestureRecognizer.delegate = self; } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (self.viewControllers.count > 1) { return YES; } return NO; } @end 
+16
Jul 13 '15 at 14:43
source share

(Updated) Swift 4.2

I found that other published solutions overriding the delegate or setting it to zero caused unexpected behavior.

In my case, when I was at the top of the navigation stack and tried to use a gesture to push another one, it would fail (as expected), but subsequent attempts to push it onto the stack would start to cause strange graphical crashes in the Navigation Bar. This makes sense because the delegate is used not only to block gesture recognition when the navigation bar is hidden, but also to throw away all this other behavior.

From my testing, gestureRecognizer(_:, shouldReceiveTouch:) that gestureRecognizer(_:, shouldReceiveTouch:) is a method that implements the original delegate to block gesture recognition when the navigation bar is hidden, and not gestureRecognizerShouldBegin(_:) . Other solutions that implement gestureRecognizerShouldBegin(_:) in their gestureRecognizerShouldBegin(_:) work because the lack of implementation of gestureRecognizer(_:, shouldReceiveTouch:) will cause the default behavior of getting all touches.

The @Nathan Perry solution is approaching, but without the responsedsToSelector respondsToSelector(_:) implementation, the UIKit code that sends messages to the delegate will assume that there is no implementation for any of the other delegate methods, and forwardingTargetForSelector(_:) will never get called.

So, we take control of 'gestRecognizer (_:, shouldReceiveTouch :) in one specific scenario in which we want to change the behavior, and otherwise redirect everything else to the delegate.

 import Foundation class AlwaysPoppableNavigationController: UINavigationController { private let alwaysPoppableDelegate = AlwaysPoppableDelegate() override func viewDidLoad() { super.viewDidLoad() alwaysPoppableDelegate.originalDelegate = interactivePopGestureRecognizer?.delegate alwaysPoppableDelegate.navigationController = self interactivePopGestureRecognizer?.delegate = alwaysPoppableDelegate } } final class AlwaysPoppableDelegate: NSObject, UIGestureRecognizerDelegate { weak var navigationController: UINavigationController? weak var originalDelegate: UIGestureRecognizerDelegate? override func responds(to aSelector: Selector!) -> Bool { if aSelector == #selector(gestureRecognizer(_:shouldReceive:)) { return true } else if let responds = originalDelegate?.responds(to: aSelector) { return responds } else { return false } } override func forwardingTarget(for aSelector: Selector!) -> Any? { return originalDelegate } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { if let nav = navigationController, nav.isNavigationBarHidden, nav.viewControllers.count > 1 { return true } else if let result = originalDelegate?.gestureRecognizer?(gestureRecognizer, shouldReceive: touch) { return result } else { return false } } } 
+15
Jun 26 '16 at 20:26
source share

Creating an answer Hunter Maximillion Monk , I subclassed the UINavigationController and then set a custom class for my UINavigationController in my storyboard. The final code for the two classes is as follows:

InteractivePopRecognizer:

 class InteractivePopRecognizer: NSObject { // MARK: - Properties fileprivate weak var navigationController: UINavigationController? // MARK: - Init init(controller: UINavigationController) { self.navigationController = controller super.init() self.navigationController?.interactivePopGestureRecognizer?.delegate = self } } extension InteractivePopRecognizer: UIGestureRecognizerDelegate { func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return (navigationController?.viewControllers.count ?? 0) > 1 } // This is necessary because without it, subviews of your top controller can cancel out your gesture recognizer on the edge. func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } } 

HiddenNavBarNavigationController:

 class HiddenNavBarNavigationController: UINavigationController { // MARK: - Properties private var popRecognizer: InteractivePopRecognizer? // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() setupPopRecognizer() } // MARK: - Setup private func setupPopRecognizer() { popRecognizer = InteractivePopRecognizer(controller: self) } } 

Storyboard:

Storyboard nav controller custom class

+8
Apr 05 '17 at 2:04 on
source share

It seems that the solution provided by @ChrisVasseli is the best. I would like to provide the same solution in Objective-C, because the question is about Objective-C (see tags)

 @interface InteractivePopGestureDelegate : NSObject <UIGestureRecognizerDelegate> @property (nonatomic, weak) UINavigationController *navigationController; @property (nonatomic, weak) id<UIGestureRecognizerDelegate> originalDelegate; @end @implementation InteractivePopGestureDelegate - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { if (self.navigationController.navigationBarHidden && self.navigationController.viewControllers.count > 1) { return YES; } else { return [self.originalDelegate gestureRecognizer:gestureRecognizer shouldReceiveTouch:touch]; } } - (BOOL)respondsToSelector:(SEL)aSelector { if (aSelector == @selector(gestureRecognizer:shouldReceiveTouch:)) { return YES; } else { return [self.originalDelegate respondsToSelector:aSelector]; } } - (id)forwardingTargetForSelector:(SEL)aSelector { return self.originalDelegate; } @end @interface NavigationController () @property (nonatomic) InteractivePopGestureDelegate *interactivePopGestureDelegate; @end @implementation NavigationController - (void)viewDidLoad { [super viewDidLoad]; self.interactivePopGestureDelegate = [InteractivePopGestureDelegate new]; self.interactivePopGestureDelegate.navigationController = self; self.interactivePopGestureDelegate.originalDelegate = self.interactivePopGestureRecognizer.delegate; self.interactivePopGestureRecognizer.delegate = self.interactivePopGestureDelegate; } @end 
+7
Jan 27 '17 at 13:30
source share

My solution is to directly extend the UINavigationController class:

 import UIKit extension UINavigationController: UIGestureRecognizerDelegate { override open func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.interactivePopGestureRecognizer?.delegate = self } public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return self.viewControllers.count > 1 } } 

Thus, all navigation controllers will be reset by sliding.

+4
Aug 29 '18 at 13:48
source share

You can do this with a proxy delegate. When you build a navigation controller, take an existing delegate. And pass it to the proxy. Then pass all the delegate methods to the existing delegate except gestureRecognizer:shouldReceiveTouch: using forwardingTargetForSelector:

Setup:

 let vc = UIViewController(nibName: nil, bundle: nil) let navVC = UINavigationController(rootViewController: vc) let bridgingDelegate = ProxyDelegate() bridgingDelegate.existingDelegate = navVC.interactivePopGestureRecognizer?.delegate navVC.interactivePopGestureRecognizer?.delegate = bridgingDelegate 

Proxy Delegate:

 class ProxyDelegate: NSObject, UIGestureRecognizerDelegate { var existingDelegate: UIGestureRecognizerDelegate? = nil override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? { return existingDelegate } func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool { return true } } 
+3
Mar 11 '16 at 3:14
source share

Simple, no side effects

Although most of the answers here are good, they seem to have unintended side effects (breaking the application) or verbose.

The simplest but most functional solution I could come up with was the following:

In the ViewController, which you hide the navigation bar,

 class MyNoNavBarViewController: UIViewController { // needed for reference when leaving this view controller var initialInteractivePopGestureRecognizerDelegate: UIGestureRecognizerDelegate? override func viewDidLoad() { super.viewDidLoad() // we will need a reference to the initial delegate so that when we push or pop.. // ..this view controller we can appropriately assign back the original delegate initialInteractivePopGestureRecognizerDelegate = self.navigationController?.interactivePopGestureRecognizer?.delegate } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(true) // we must set the delegate to nil whether we are popping or pushing to.. // ..this view controller, thus we set it in viewWillAppear() self.navigationController?.interactivePopGestureRecognizer?.delegate = nil } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(true) // and every time we leave this view controller we must set the delegate back.. // ..to what it was originally self.navigationController?.interactivePopGestureRecognizer?.delegate = initialInteractivePopGestureRecognizerDelegate } } 

Other answers suggest just setting the delegate to zero. Swiping back to the initial view controller in the navigation stack disables all gestures. Perhaps this is a kind of oversight of the developers of UIKit / UIGesture.

In addition, some of the answers that I implemented here led to Apple's abnormal behavior (in particular, the ability to scroll up or down while moving backward). These answers also seem a bit verbose, and in some cases incomplete.

+3
Jul 12 '19 at 17:21
source share

Reply to Xamarin:

IUIGestureRecognizerDelegate interface in the IUIGestureRecognizerDelegate class definition:

 public partial class myViewController : UIViewController, IUIGestureRecognizerDelegate 

In your ViewController add the following method:

 [Export("gestureRecognizerShouldBegin:")] public bool ShouldBegin(UIGestureRecognizer recognizer) { if (recognizer is UIScreenEdgePanGestureRecognizer && NavigationController.ViewControllers.Length == 1) { return false; } return true; } 

In your ViewController ViewDidLoad() add the following line:

 NavigationController.InteractivePopGestureRecognizer.Delegate = this; 
+1
Aug 21 '15 at 1:43 on
source share

I tried this and it works great: How to hide the navigation bar without losing the slide feature

The idea is to implement "UIGestureRecognizerDelegate" in your .h and add this to your .m file.

 - (void)viewWillAppear:(BOOL)animated { // hide nav bar [[self navigationController] setNavigationBarHidden:YES animated:YES]; // enable slide-back if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enabled = YES; self.navigationController.interactivePopGestureRecognizer.delegate = self; } } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { return YES; } 
+1
Jun 14 '16 at 13:33
source share

Here is my solution: I am changing the alpha in the navigation bar, but the navigation bar is not hidden. All my view controllers are a subclass of my BaseViewController, and there I have it:

  override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) navigationController?.navigationBar.alpha = 0.0 } 

You can also subclass UINavigationController and put this method there.

+1
May 16 '19 at 11:25
source share

Some people succeeded by calling the setNavigationBarHidden method with animated YES .

0
Jul 12 '14 at 8:07
source share

In my opinion, the controller without the navigation bar I use

 open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) CATransaction.begin() UIView.animate(withDuration: 0.25, animations: { [weak self] in self?.navigationController?.navigationBar.alpha = 0.01 }) CATransaction.commit() } open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) CATransaction.begin() UIView.animate(withDuration: 0.25, animations: { [weak self] in self?.navigationController?.navigationBar.alpha = 1.0 }) CATransaction.commit() } 

During the interactive dismissal, the back button will scroll, so I hid it.

0
Dec 02 '16 at 12:45
source share

There is a very simple solution that I tried and that works fine, it is in Xamarin.iOS, but can be applied to the native one:

  public override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); this.NavigationController.SetNavigationBarHidden(true, true); } public override void ViewDidAppear(bool animated) { base.ViewDidAppear(animated); this.NavigationController.SetNavigationBarHidden(false, false); this.NavigationController.NavigationBar.Hidden = true; } public override void ViewWillDisappear(bool animated) { base.ViewWillDisappear(animated); this.NavigationController.SetNavigationBarHidden(true, false); } 
0
Oct 03
source share

Here's how to disable gesture recognizer when a user exits ViewController. You can paste it into your viewWillAppear () or into the ViewDidLoad () methods.

 if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enabled = NO; } 
-6
Feb 06 '15 at 20:59
source share



All Articles