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

mih*_*hai 75 xcode objective-c uinavigationcontroller uigesturerecognizer ios

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

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

在此输入图像描述

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

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

有没有办法隐藏顶部导航栏仍然有滑动?

Hor*_*seT 92

正在工作的一个黑客是设置interactivePopGestureRecognizer的委托的UINavigationController,以nil这样的:

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];
Run Code Online (Sandbox Code Playgroud)

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

  • "当堆叠中只有一个视图控制器时,反复向后扫描会导致手势被识别,这反过来又会将UI放入(我认为是UIKit工程师意外的)状态,它会停止识别任何手势" (12认同)
  • 可以防止意外状态的替代方法是将其设置为某个低级对象(我使用我的app委托)并实现`gestureRecognizerShouldBegin`,如果`navigationController`的`viewController`计数更大则返回`true`比0. (4认同)
  • 虽然这有效,但我强烈建议不要这样做.打破委托导致罕见且难以识别主线程块.原来它不是主线程块,而是@HorseT所描述的. (3认同)
  • 我的应用程序保存委托句柄,然后在`viewWillDisappear`中恢复它,到目前为止还没有遇到不良副作用. (2认同)

Hun*_*onk 63

其他方法的问题

设置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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,将导航控制器设置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
}
Run Code Online (Sandbox Code Playgroud)

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

  • 停止为我在iOS 13上工作 (3认同)
  • 很棒的解决方案! (2认同)
  • 最佳答案,谢谢! (2认同)
  • @HunterMaximillionMonk感谢您提供了出色的解决方案.它就像一个魅力 (2认同)
  • 绝对是最佳答案! (2认同)
  • 适用于 iOS 13.5、12.4.6 和 10.3.4。谢谢。 (2认同)

sar*_*pol 53

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

根视图控制器

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
}
Run Code Online (Sandbox Code Playgroud)

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

  • 有时我在使用它时会得到EXC_BAD_ACCESS (2认同)
  • 记得将`UIGestureRecognizerDelegate` 添加到根视图控制器......在我的例子中,委托在比根视图控制器晚的视图控制器中设置为 nil,所以当返回到根视图控制器时,没有调用 `gestureRecognizerShouldBegin` . 所以我把 `.delegate = self` 放在 `viewDidAppear()` 中。这解决了我的情况下的奇怪效果..干杯! (2认同)

Chr*_*lli 17

(更新)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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个不错的解决方案在 iOS 13.4 中不再起作用 (3认同)
  • 这会导致内存泄漏,因为 `navigationController` 是 AlwaysPoppableDelegate 中的强引用。我已经编辑了代码,使其成为“弱”参考。 (2认同)

Yog*_*ari 16

您可以将UINavigationController子类化如下:

@interface CustomNavigationController : UINavigationController<UIGestureRecognizerDelegate>

@end
Run Code Online (Sandbox Code Playgroud)

执行:

@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
Run Code Online (Sandbox Code Playgroud)

  • 使用这种方法会破坏 UIPageViewController 滚动中的弹出手势。 (2认同)

tyl*_*ner 8

基于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
    }
}
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

故事板:

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


Tim*_*ich 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
Run Code Online (Sandbox Code Playgroud)

  • 因为 ObjC 还没有死! (3认同)
  • 这是正确的解决方案。不转发给原始委托的任何其他解决方案都是不正确的。 (2认同)

fre*_*dnd 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
    }

}
Run Code Online (Sandbox Code Playgroud)

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


小智 6

简单,无副作用答案

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

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

在要隐藏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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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


小智 5

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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我已经在带有“iOS 13.4.1”的真实设备 iPhone 6S Plus 上对此进行了测试,并且向后滑动可以正常工作。 (2认同)
  • 很好的解决方案,在 iOS 14.5(beta 2)上进行了测试并且仍然有效。请记住,preferredStatusBarStyle 将不再在视图控制器中被调用。它必须由自定义导航控制器处理。 (2认同)