在UINavigationController中隐藏导航栏时没有向后滑动

时间:2014-07-12 06:21:22

标签: ios objective-c xcode uinavigationcontroller uigesturerecognizer

我喜欢在UINavigationController中嵌入视图所继承的滑动包。不幸的是,我似乎找不到隐藏导航栏的方法,但仍然有触摸平移向后滑动手势。我可以编写自定义手势,但我不喜欢和依赖UINavigationController反向滑动手势。

如果我在故事板中取消选中它,则后滑动不起作用

enter image description here

或者如果我以编程方式隐藏它,那就是相同的场景。

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

是否无法隐藏顶部导航栏并仍然可以滑动?

19 个答案:

答案 0 :(得分:87)

正在运行的黑客是将interactivePopGestureRecognizer的{​​{1}}委托设置为UINavigationController,如下所示:

nil

但在某些情况下,它可能会产生奇怪的效果。

答案 1 :(得分:53)

其他方法的问题

设置interactivePopGestureRecognizer.delegate = nil会产生意想不到的副作用。

设置navigationController?.navigationBar.hidden = true确实有效,但不允许隐藏导航栏中的更改。

最后,通常更好的做法是为导航控制器创建UIGestureRecognizerDelegate的模型对象。将其设置为UINavigationController堆栈中的控制器是导致EXC_BAD_ACCESS错误的原因。

完整解决方案

首先,将此类添加到项目中:

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

然后,将导航控制器的interactivePopGestureRecognizer.delegate设置为新InteractivePopRecognizer课程的实例。

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
}

享受隐藏的导航栏,没有任何副作用,即使您的顶级控制器具有表格,集合或滚动视图子视图也能正常工作。

答案 2 :(得分:48)

在我的情况下,为了防止奇怪的效果

根视图控制器

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/

答案 3 :(得分:16)

您可以将UINavigationController子类化如下:

@interface CustomNavigationController : UINavigationController<UIGestureRecognizerDelegate>

@end

实现:

@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

答案 4 :(得分:14)

(更新)Swift 4.2

我发现其他已发布的解决方案覆盖了委托,或者将其设置为nil会导致一些意外行为。

在我的情况下,当我在导航堆栈的顶部并尝试使用手势再弹出一次时,它将失败(如预期的那样),但随后尝试推入堆栈将开始导致奇怪的图形导航栏中的故障。这是有道理的,因为委托被用来处理的不仅仅是在隐藏导航栏时是否阻止手势被识别,而且所有其他行为都被抛弃了。

根据我的测试,似乎gestureRecognizer(_:, shouldReceiveTouch:)是原始委托实施的方法,用于阻止在隐藏导航栏时识别手势,而不是gestureRecognizerShouldBegin(_:)。在其委托工作中实现gestureRecognizerShouldBegin(_:)的其他解决方案,因为缺少gestureRecognizer(_:, shouldReceiveTouch:)的实现将导致接收所有触摸的默认行为。

@Nathan Perry的解决方案已接近,但如果没有respondsToSelector(_:)的实现,向委托发送消息的UIKit代码将认为没有任何其他委托方法的实现,forwardingTargetForSelector(_:)永远不会被召唤。

因此,我们在我们想要修改行为的一个特定场景中控制`gestureRecognizer(_:,shouldReceiveTouch :),否则将其他所有内容转发给委托。

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

答案 5 :(得分:8)

建立Hunter Maximillion Monk's answer,我为UINavigationController创建了一个子类,然后在我的故事板中为我的UINavigationController设置了自定义类。这两个类的最终代码如下所示:

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 nav controller custom class

答案 6 :(得分:7)

看起来@ChrisVasseli提供的解决方案是最好的。我想在Objective-C中提供相同的解决方案,因为问题是关于Objective-C(参见标签)

@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 :(得分:4)

Hunter Monk的回答确实很棒,但不幸的是,在iOS 13.3.1中,它不起作用。

我将说明另一种隐藏UINavigationBar而不丢失swipe to back gesture的方法。 我已经在iOS 13.3.1和12.4.3上进行了测试,并且可以使用。

您需要创建一个自定义类UINavigationController,并在UINavigationController中为Storyboard设置该类

Set custom class to <code>UINavigationController</code>

请勿在{{1​​}}上隐藏NavigationBar

<code>UINavigationController</code> Attributes inspector:

Storyboard上的示例:

Storyboard:

最后,将其放置在Storyboard类的navigationBar.isHidden = true中的viewDidLoad

请确保不要使用此方法CustomNavigationController隐藏setNavigationBarHidden(true, animated: true)

NavigationBar

答案 8 :(得分:3)

您可以使用代理委派进行此操作。在构建导航控制器时,请抓住现有委托。并将其传递给代理。然后使用gestureRecognizer:shouldReceiveTouch:

将所有委托方法传递给除forwardingTargetForSelector:之外的现有委托

设定:

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

代理委托:

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

答案 9 :(得分:3)

我的解决方案是直接扩展UINavigationController类:

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
    }

}

这样,所有的导航控制器都可以通过滑动关闭。

答案 10 :(得分:1)

TLDR- 没有任何副作用的解决方案:

不是从故事板创建 UINavigationController,而是创建一个继承 UINavigationController 的自定义类并通过代码呈现它。

class RootNavigationController: UINavigationController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationBar.isHidden = true
    }
}

let rootNavVC = RootNavigationController(rootViewController: vc)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
    appDelegate.window?.rootViewController = rootNavVC
}

尝试了其他解决方案:

  1. interactivePopGestureRecognizer.delegate = nil 导致随机行为。

  2. 设置interactivePopGestureRecognizer.delegate = self,然后在viewDidAppear 或其他地方执行此操作。

    如果 navigationController?.viewControllers.count ?? 0 > 1 { navigationController?.interactivePopGestureRecognizer?.isEnabled = true } 别的 { navigationController?.interactivePopGestureRecognizer?.isEnabled = false }

只要堆栈中有 1 个以上的 viewController,这就能正常工作。如果计数 <= 1,应用程序会冻结。

答案 11 :(得分:1)

简单,没有副作用的答案

虽然这里的大多数答案都不错,但它们似乎具有意想不到的副作用(应用程序中断)或冗长。

我能想到的最简单但最实用的解决方案是:

在ViewController中,您隐藏了navigationBar,

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

其他答案建议仅将委托设置为nil。向后滑动到导航堆栈上的初始视图控制器会导致所有手势被禁用。可能是对UIKit / UIGesture开发人员的某种监督。

同样,我在此处执行的一些答案导致了非标准的Apple导航行为(特别是允许在向上或向下滚动的同时还向后滑动的功能)。这些答案似乎也很冗长,在某些情况下还不完整。

答案 12 :(得分:1)

我试过这个并且它完美地工作: How to hide Navigation Bar without losing slide-back ability

我们的想法是在你的.h中实现“UIGestureRecognizerDelegate” 并将其添加到.m文件中。

- (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;  
}

答案 13 :(得分:1)

Xamarin答案:

在ViewController的类定义中实现IUIGestureRecognizerDelegate接口:

public partial class myViewController : UIViewController, IUIGestureRecognizerDelegate

在ViewController中添加以下方法:

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

在ViewController&#39; ViewDidLoad()中添加以下行:

NavigationController.InteractivePopGestureRecognizer.Delegate = this;

答案 14 :(得分:0)

在没有导航栏的视图控制器中,我使用

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

在交互式解雇期间,后退按钮会闪烁,这就是我隐藏它的原因。

答案 15 :(得分:0)

有一个非常简单的解决方案,我尝试过并且可以完美地工作,它在Xamarin.iOS中,但是也可以应用于本机:

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

答案 16 :(得分:0)

这是我的解决方案: 我正在更改导航栏上的Alpha,但导航栏未隐藏。 我所有的视图控制器都是BaseViewController的子类,并且在那里:

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

您还可以继承UINavigationController的子类,并将该方法放在那里。

答案 17 :(得分:0)

通过使用动画setNavigationBarHidden调用YES方法,

Some people取得了成功。

答案 18 :(得分:-6)

以下是当用户滑出ViewController时如何禁用de手势识别器。您可以将其粘贴到viewWillAppear()或ViewDidLoad()方法上。

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