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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24710258/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:21:37  来源:igfitidea点击:

No Swipe Back when hiding Navigation Bar in UINavigationController

iosobjective-cxcodeuinavigationcontrolleruigesturerecognizer

提问by mihai

I love the swipe pack thats inherited from embedding your views in a UINavigationController. Unfortunately i cannot seem to find a way to hide the NavigationBarbut still have the touch pan swipe back gesture. I can write custom gestures but I prefer not to and to rely on the UINavigationControllerback swipe gestureinstead.

我喜欢从将您的视图嵌入到UINavigationController. 不幸的是,我似乎无法找到一种方法来隐藏NavigationBar但仍然让触摸板向后滑动gesture。我可以编写自定义手势,但我不想这样做,而是依靠UINavigationController向后滑动gesture

if I uncheck it in the storyboard, the back swipe doesn't work

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

enter image description here

在此处输入图片说明

alternatively if I programmatically hide it, the same scenario.

或者,如果我以编程方式隐藏它,同样的场景。

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

Is there no way to hide the top NavigationBarand still have the swipe?

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

回答by HorseT

A hack that is working is to set the interactivePopGestureRecognizer's delegate of the UINavigationControllerto nillike this:

一个有效的技巧是将interactivePopGestureRecognizer的委托设置UINavigationControllernil这样:

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];

But in some situations it could create strange effects.

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

回答by Hunter Monk

Problems with Other Methods

其他方法的问题

Setting the interactivePopGestureRecognizer.delegate = nilhas unintended side-effects.

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

Setting navigationController?.navigationBar.hidden = truedoes work, but does not allow your change in navigation bar to be hidden.

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

Lastly, it's generally better practice to create a model object that is the UIGestureRecognizerDelegatefor your navigation controller. Setting it to a controller in the UINavigationControllerstack is what is causing the EXC_BAD_ACCESSerrors.

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

Full 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, set your navigation controller's interactivePopGestureRecognizer.delegateto an instance of your new InteractivePopRecognizerclass.

然后,将导航控制器设置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
}

Enjoy a hidden navigation bar with no side effects, that works even if your top controller has table, collection, or scroll view subviews.

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

回答by saranpol

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/

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

回答by Chris Vasselli

Updated for iOS 13.4

针对 iOS 13.4 更新

iOS 13.4 broke the previous solution, so things are gonna get ugly. It looks like in iOS 13.4 this behavior is now controlled by a private method _gestureRecognizer:shouldReceiveEvent:(not to be confused with the new public shouldReceivemethod added in iOS 13.4).

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



I found that other posted solutions overriding the delegate, or setting it to nil caused some unexpected behavior.

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

In my case, when I was on the top of the navigation stack and tried to use the gesture to pop one more, it would fail (as expected), but subsequent attempts to push onto the stack would start to cause weird graphical glitches in the navigation bar. This makes sense, because the delegate is being used to handle more than just whether or not to block the gesture from being recognized when the navigation bar is hidden, and all that other behavior was being thrown out.

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

From my testing, it appears that gestureRecognizer(_:, shouldReceiveTouch:)is the method that the original delegate is implementing to block the gesture from being recognized when the navigation bar is hidden, not gestureRecognizerShouldBegin(_:). Other solutions that implement gestureRecognizerShouldBegin(_:)in their delegate work because the lack of an implementation of gestureRecognizer(_:, shouldReceiveTouch:)will cause the default behavior of receiving all touches.

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

@Nathan Perry's solution gets close, but without an implementation of respondsToSelector(_:), the UIKit code that sends messages to the delegate will believe there is no implementation for any of the other delegate methods, and forwardingTargetForSelector(_:)will never get called.

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

So, we take control of `gestureRecognizer(_:, shouldReceiveTouch:) in the one specific scenario we want to modify the behavior, and otherwise forward everything else to the delegate.

因此,我们在要修改行为的一个特定场景中控制 `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
        }
    }
}

回答by Yogesh Maheshwari

You can subclass UINavigationController as following:

您可以将 UINavigationController 子类化,如下所示:

@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

回答by CodyB

Simple, no side-effect Answer

简单,无副作用 答案

While most answers here are good, they seemingly have unintended side-effects (app breaking) or are verbose.

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

The most simple yet functional solution I could come up with was the following:

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

In the ViewController that you are hiding the 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
    }
}

Other answers have suggested merely setting the delegate to nil. Swiping backwards to the initial view controller on the navigation stack results in all gestures to be disabled. Some sort of oversight, perhaps, of the UIKit/UIGesture devs.

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

As well, some answers here that I have implemented resulted in non-standard apple navigation behaviour (specifically, allowing for the ability to scroll up or down while also swiping backwards). These answers also seem a bit verbose and in some cases incomplete.

同样,我在这里实现的一些答案导致了非标准的苹果导航行为(特别是允许向上或向下滚动同时向后滑动的能力)。这些答案似乎也有点冗长,在某些情况下不完整。

回答by tylermilner

Building off of Hunter Maximillion Monk's answer, I made a subclass for UINavigationController and then set the custom class for my UINavigationController in my storyboard. Final code for the two classes looks like this:

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

InteractivePopRecognizer:

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:

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

Storyboard 导航控制器自定义类

回答by Timur Bernikovich

Looks like solution provided by @ChrisVasseli is the best. I'd like to provide same solution in Objective-C because question is about Objective-C (see tags)

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

回答by fredericdnd

My solution is to directly extend the UINavigationControllerclass :

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

}

This way, all navigation controllers will be dismissable by sliding.

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

回答by Nathan Perry

You can do it with a Proxy Delegate. When you are building the navigation controller, grab the existing delegate. And pass it into the proxy. Then pass all delegate methods to the existing delegate except gestureRecognizer:shouldReceiveTouch:using forwardingTargetForSelector:

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