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
No Swipe Back when hiding Navigation Bar in UINavigationController
提问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 NavigationBar
but still have the touch pan swipe back gesture
. I can write custom gestures but I prefer not to and to rely on the UINavigationController
back swipe gesture
instead.
我喜欢从将您的视图嵌入到UINavigationController
. 不幸的是,我似乎无法找到一种方法来隐藏NavigationBar
但仍然让触摸板向后滑动gesture
。我可以编写自定义手势,但我不想这样做,而是依靠UINavigationController
向后滑动gesture
。
if I uncheck it in the storyboard, the back swipe doesn't work
如果我在故事板中取消选中它,则向后滑动不起作用
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 NavigationBar
and still have the swipe?
有没有办法隐藏顶部NavigationBar
并且仍然可以滑动?
回答by HorseT
A hack that is working is to set the interactivePopGestureRecognizer
's delegate of the UINavigationController
to nil
like this:
一个有效的技巧是将interactivePopGestureRecognizer
的委托设置UINavigationController
为nil
这样:
[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 = nil
has unintended side-effects.
设置interactivePopGestureRecognizer.delegate = nil
有意想不到的副作用。
Setting navigationController?.navigationBar.hidden = true
does 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 UIGestureRecognizerDelegate
for your navigation controller. Setting it to a controller in the UINavigationController
stack is what is causing the EXC_BAD_ACCESS
errors.
最后,创建一个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.delegate
to an instance of your new InteractivePopRecognizer
class.
然后,将导航控制器设置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 shouldReceive
method added in iOS 13.4).
iOS 13.4 打破了之前的解决方案,所以事情会变得很糟糕。看起来在 iOS 13.4 中,此行为现在由私有方法控制_gestureRecognizer:shouldReceiveEvent:
(不要与shouldReceive
iOS 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:
故事板:
回答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 UINavigationController
class :
我的解决方案是直接扩展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
}
}