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


86

我喜欢从将您的视图嵌入到NET中继承的滑动包UINavigationController。不幸的是,我似乎找不到隐藏它的方法,NavigationBar但仍然可以向后滑动触摸板gesture。我可以编写自定义手势,但我不希望这样做,而是依靠UINavigationController向后滑动gesture

如果我在情节提要图中取消选中,则后向滑动不起作用

在此处输入图片说明

或者,如果我以编程方式将其隐藏,则采用相同的方案。

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

有没有办法隐藏顶部NavigationBar并仍然可以滑动?


1
添加UIGestureRecognizer是否可以接受?实施起来很容易。
SwiftArchitect

1
@LancelotdelaMare,我正试图避免这种情况,因为它无法像UINavigationController向后滑动一样流畅。我正在研究UIScreenEdgePanGestureRecognizer,因为有人说它有帮助,但尚未使它起作用。在这里寻找最简单,最优雅的解决方案。
mihai 2014年

Answers:


96

正在工作的黑客攻击是在设定interactivePopGestureRecognizer的的委托UINavigationController,以nil这样的:

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];

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


16
“当堆栈上只有一个视图控制器时,反复回扫可能会导致手势被识别,这又将UI置于(我认为UIKit工程师出乎意料的状态),在该状态下它将停止识别任何手势”
HorseT

4
可能防止意料之外的状态另一种方法是将其设置为一些低级别的对象(我用我的应用程序委托)和实施gestureRecognizerShouldBegin,返回true如果navigationControllerviewController计数大于0
肯尼·温克

4
尽管这可行,但我强烈建议您反对。破坏委托导致了罕见且难以识别的主线程块。原来它不是主线程块,而是@HorseT描述的内容。
2015年

3
我的应用程序保存了委托句柄,然后将其还原,viewWillDisappear到目前为止,还没有遇到不利的副作用。
Don Park

1
!!!! 强烈建议不要使用此解决方案,当反复使用滑动功能时,会发生奇怪的行为,后背被禁用,整个应用程序不再响应
-KarimIhab

79

其他方法的问题

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

享受一个无副作用的隐藏导航栏,即使您的顶级控制器具有表,集合或滚动视图子视图,该导航栏也可以使用。


1
很好的解决方案!
马特·巴特勒

1
最好的答案,谢谢!
多莉·丹尼尔

2
@HunterMaximillionMonk感谢您提供的出色解决方案。它像一种魅力一样工作
如diu

1
@HunterMaximillionMonk这似乎正常工作,但是当我有多个控制器时出现问题,然后在一段时间后弹出它停止工作。
Premal Khetani

1
绝对是最佳答案!
daxh

54

就我而言,以防止产生奇怪的影响

根视图控制器

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/


2
有时在使用此工具时会得到EXC_BAD_ACCESS
Andrey Gordeev

对我来说,它无法使手势正常工作,并经常因EXEC_BAD_ACCESS
Benjohn

2
记住要添加UIGestureRecognizerDelegate到根视图控制器中...在我的情况下,在比根视图控制器更高的视图控制器中将委托设置为nil,因此当返回到根视图控制器时,gestureRecognizerShouldBegin不会被调用。所以,我把.delegate = selfviewDidAppear()。在我看来,这解决了奇怪的影响。
Wiingaard

@AndreyGordeev您能否提供一些有关何时EXEC_BAD_ACCESS发生的详细信息?
Jaybo

下面是有关详细信息EXC_BAD_ACCESS的错误:stackoverflow.com/questions/28746123/...
安德烈Gordeev

25

更新于iOS 13.4

iOS 13.4打破了以前的解决方案,因此事情变得很丑陋。看起来在iOS 13.4中,此行为现在由私有方法控制_gestureRecognizer:shouldReceiveEvent:(不要与shouldReceiveiOS 13.4中添加的新public方法混淆)。


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

就我而言,当我位于导航堆栈的顶部并尝试使用该手势再弹出一个时,它将失败(如预期的那样),但是随后尝试推入堆栈将开始在导航栏中引起奇怪的图形故障导航栏。这是有道理的,因为在隐藏导航栏并抛出所有其他行为时,委托不仅用于处理是否阻止手势识别,还用于处理委托。

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

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

因此,在我们要修改行为的一种特定情况下,我们控制“ gestureRecognizer(_ :, shouldReceiveTouch :)”,否则将其他所有内容转发给委托。

class AlwaysPoppableNavigationController : UINavigationController {

    private var alwaysPoppableDelegate: AlwaysPoppableDelegate!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.alwaysPoppableDelegate = AlwaysPoppableDelegate(navigationController: self, originalDelegate: self.interactivePopGestureRecognizer!.delegate!)
        self.interactivePopGestureRecognizer!.delegate = self.alwaysPoppableDelegate
    }
}

private class AlwaysPoppableDelegate : NSObject, UIGestureRecognizerDelegate {

    weak var navigationController: AlwaysPoppableNavigationController?
    weak var originalDelegate: UIGestureRecognizerDelegate?

    init(navigationController: AlwaysPoppableNavigationController, originalDelegate: UIGestureRecognizerDelegate) {
        self.navigationController = navigationController
        self.originalDelegate = originalDelegate
    }

    // For handling iOS before 13.4
    @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if let navigationController = navigationController, navigationController.isNavigationBarHidden && navigationController.viewControllers.count > 1 {
            return true
        }
        else if let originalDelegate = originalDelegate {
            return originalDelegate.gestureRecognizer!(gestureRecognizer, shouldReceive: touch)
        }
        else {
            return false
        }
    }

    // For handling iOS 13.4+
    @objc func _gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceiveEvent event: UIEvent) -> Bool {
        if let navigationController = navigationController, navigationController.isNavigationBarHidden && navigationController.viewControllers.count > 1 {
            return true
        }
        else if let originalDelegate = originalDelegate {
            let selector = #selector(_gestureRecognizer(_:shouldReceiveEvent:))
            if originalDelegate.responds(to: selector) {
                let result = originalDelegate.perform(selector, with: gestureRecognizer, with: event)
                return result != nil
            }
        }

        return false
    }

    override func responds(to aSelector: Selector) -> Bool {
        if #available(iOS 13.4, *) {
            // iOS 13.4+ does not need to override responds(to:) behavior, it only uses forwardingTarget
            return originalDelegate?.responds(to: aSelector) ?? false
        }
        else {
            if aSelector == #selector(gestureRecognizer(_:shouldReceive:)) {
                return true
            }
            else {
                return originalDelegate?.responds(to: aSelector) ?? false
            }
        }
    }

    override func forwardingTarget(for aSelector: Selector) -> Any? {
        if #available(iOS 13.4, *), aSelector == #selector(_gestureRecognizer(_:shouldReceiveEvent:)) {
            return nil
        }
        else {
            return self.originalDelegate
        }
    }
}

1
目前看来您的解决方案是最好的。谢谢!
Timur Bernikovich

“但是随后尝试推入堆栈的尝试将开始在导航栏中引起奇怪的图形故障” –我对此感到困惑。我以为我们没有导航栏?这就是问题所在?在我的情况下,我将导航控制器嵌入为没有导航栏的子视图控制器;包含的VC具有导航控件。因此,我让包含的VC成为识别器的委托,并做了相应的gestureRecognizerShouldBegin:事情,它“似乎可以工作”。想知道我应该注意些什么。
skagedal '17

2
由于这navigationController是AlwaysPoppableDelegate中的强引用,因此发生了内存泄漏。我已经编辑了代码以使其成为weak参考。
格雷厄姆·珀克斯

3
这个不错的解决方案在iOS 13.4中无法正常工作
Ely

@ChrisVasselli真的很棒,谢谢!希望这将通过App Store评论的私有方法检查。
Ely

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

2
使用这种方法可以打破UIPageViewController过度滚动中的弹出手势。
atulkhatri

我发现viewController.count> 1检查是必要的。如果用户尝试仅使用根VC向后滑动,则UI将在下次VC推送时挂起。
VaporwareWolf

11

简单,无副作用答案

尽管此处的大多数答案都不错,但它们似乎具有意想不到的副作用(应用程序中断)或过于冗长。

我能想到的最简单但功能齐全的解决方案是:

在要隐藏NavigationBar的ViewController中,

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导航行为(具体而言,允许向上或向下滚动同时向后滑动)。这些答案似乎也很冗长,在某些情况下还不完整。


viewDidLoad()这不是一个理想的捕获位置,initialInteractivePopGestureRecognizerDelegate因为它navigationController可能在此处为零(尚未压入堆栈)。viewWillAppear您隐藏导航栏的位置会更合适
nCod3d

10

基于Hunter Maximillion Monk的回答,我为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)
    }
}

故事板:

故事板导航控制器自定义类


8

看起来@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

3
因为ObjC还没有死!😉–
MonsieurDart

2
这是正确的解决方案。任何其他不转发给原始委托的解决方案都是不正确的。
Josh Bernfeld '17

6

我的解决方案是直接扩展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
    }

}

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


奇怪的是,这将导致viewDidAppear忽略属于任何导航控制器的VC上的所有调用。
cumanzor

4

您可以使用代理委托来完成。在构建导航控制器时,请抓住现有的委托。并将其传递给代理。然后将所有委托方法传递给现有委托,除了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
    }  
}

这个答案是真的Obj-C风格!
Sound Blaster

据我所知,forwardingTargetForSelector可以为我节省过去一个项目的大量时间。好东西!
VaporwareWolf

4

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

我将解释隐藏UINavigationBar而不失落的另一种方式swipe to back gesture。我已经在iOS 13.3.1和12.4.3上进行了测试,并且可以正常工作。

你需要创建一个自定义类UINavigationController,并设置类UINavigationControllerStoryboard

将自定义类设置为<code> UINavigationController </ code>

不要隐藏NavigationBarStoryboard

<code> UINavigationController </ code>属性检查器:

范例Storyboard

故事板:

最后,把这个:navigationBar.isHidden = trueviewDidLoadCustomNavigationController类。

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

import UIKit

class CustomNavigationController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationBar.isHidden = true
    }
}

2
我已在具有iOS 13.4.1并向后滑动功能的真实设备iPhone 6S Plus上对此进行了测试。
Emre Aydin

1
在最新的iOS 14.0.1上也可以使用
bezoadam

1

Xamarin答案:

IUIGestureRecognizerDelegate在ViewController的Class定义中实现Interface:

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中,ViewDidLoad()添加以下行:

NavigationController.InteractivePopGestureRecognizer.Delegate = this;

大概这是在UINavigationController根视图控制器中吗?EXEC_BAD_ACCESS当我尝试这个时,我得到了。
约翰

您可以在根视图控制器上进行边缘平移吗?那应该是不可能的,因为当您在根VC上时,您会弹出所有其他VC,并且Nav的VC数组的长度应
Ahmad

崩溃发生在呼叫之前gestureRecognizerShouldBegin:
Benjohn 2015年

1
您可以在新的Question或Xamarin论坛上发布VC代码吗?
艾哈迈德(Ahmad)2015年

不,我没有。我想我将其保留为.1!
约翰·

1

我已经尝试过了,并且效果很好: 如何隐藏导航栏而不会丢失滑动功能

这个想法是在您的.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;  
}

1

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

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

您也可以将UINavigationController子类化,并将该方法放在那里。


0

有些人通过setNavigationBarHidden用animated调用该方法YES而获得成功。


我没有碰运气。更新我的答案以涵盖此建议。
mihai 2014年

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

在交互式解雇过程中,后退按钮会一直闪着,这就是我隐藏它的原因。


-2

有一个非常简单的解决方案,我尝试过并且可以完美地运行,它在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);
    }

-6

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

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

在发布答案之前,请先阅读问题。问题是启用它,而不是禁用它。我们喜欢流行的手势。
Yogesh Maheshwari,2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.