设置leftBarButtonItem后如何在UINavigationController中启用后退/左滑手势?
Posted
技术标签:
【中文标题】设置leftBarButtonItem后如何在UINavigationController中启用后退/左滑手势?【英文标题】:How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem? 【发布时间】:2016-04-28 19:25:20 【问题描述】:我从here 得到了相反的问题。
默认情况下,ios7
中,UINavigationController
的堆栈的向后滑动手势可以弹出呈现的ViewController
。现在我为所有ViewControllers
统一了所有self.navigationItem.leftBarButtonItem
样式。
代码如下:
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:LOADIMAGE(@"back_button") style:UIBarButtonItemStylePlain target:self action:@selector(popCurrentViewController)];
之后,navigationController.interactivePopGestureRecognizer
被禁用。如何在不删除自定义 leftBarButtonItem
的情况下启用弹出手势?
谢谢!
【问题讨论】:
同样的问题已经有解决方案here @ian 谢谢!这意味着所有的屏幕滑动手势都是用于向后滑动,我认为这不是一个好主意。 【参考方案1】:viewDidLoad 中的第一个设置委托:
self.navigationController.interactivePopGestureRecognizer.delegate = self;
然后在推送时禁用手势:
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
[super pushViewController:viewController animated:animated];
self.interactivePopGestureRecognizer.enabled = NO;
并在 viewDidDisappear 中启用:
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
另外,将UINavigationControllerDelegate
添加到您的视图控制器。
【讨论】:
我忘了说你应该将 UINavigationControllerDelegate 添加到你的视图控制器中。 谢谢!我也刚刚在SO中找到了解决方案! (我在问之前没有搜索足够的结果,我的错!) 为什么要设置识别器的delegate?你没有提到协议方法的任何实现? 这不起作用。 @hfossli 的解决方案非常有效。虽然我在 Swift3 中尝试过。 我们怎样才能使它通用并添加到现有项目中?我们当然不想对每个视图控制器都这样做......【参考方案2】:你需要处理两种情况:
-
将新视图推送到堆栈时
当您显示根视图控制器时
如果你只需要一个可以使用的基类,这里有一个 Swift 3 版本:
import UIKit
final class SwipeNavigationController: UINavigationController
// MARK: - Lifecycle
override init(rootViewController: UIViewController)
super.init(rootViewController: rootViewController)
delegate = self
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
delegate = self
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
delegate = self
override func viewDidLoad()
super.viewDidLoad()
// This needs to be in here, not in init
interactivePopGestureRecognizer?.delegate = self
deinit
delegate = nil
interactivePopGestureRecognizer?.delegate = nil
// MARK: - Overrides
override func pushViewController(_ viewController: UIViewController, animated: Bool)
duringPushAnimation = true
super.pushViewController(viewController, animated: animated)
// MARK: - Private Properties
fileprivate var duringPushAnimation = false
// MARK: - UINavigationControllerDelegate
extension SwipeNavigationController: UINavigationControllerDelegate
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)
guard let swipeNavigationController = navigationController as? SwipeNavigationController else return
swipeNavigationController.duringPushAnimation = false
// MARK: - UIGestureRecognizerDelegate
extension SwipeNavigationController: UIGestureRecognizerDelegate
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
guard gestureRecognizer == interactivePopGestureRecognizer else
return true // default value
// Disable pop gesture in two situations:
// 1) when the pop animation is in progress
// 2) when user swipes quickly a couple of times and animations don't have time to be performed
return viewControllers.count > 1 && duringPushAnimation == false
如果您最终需要在另一个类中充当UINavigationControllerDelegate
,您可以编写一个委托转发器similar to this answer。
改编自 Objective-C 中的源代码:https://github.com/fastred/AHKNavigationController
【讨论】:
这是一个很棒且干净的解决方案 它确实很好用,谢谢 - 但你可以/应该也实现required init?(coder aDecoder: NSCoder) super.init(coder: aDecoder) delegate = self
以用于故事板:)
我添加了故事板支持:***.com/a/49750785/129202 似乎可以工作,但请随时对其进行编辑以解决任何问题。
如果某些屏幕不需要向后滑动怎么办。我需要在那些屏幕上做什么?我已经为这个委托方法返回了 false。 func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
并在 viewwillAppear 和消失方法中设置 true false self.navigationController?.interactivePopGestureRecognizer?.isEnabled
没有工作。有什么解决办法吗?
如何称呼这个类?【参考方案3】:
当我设置委托时它对我有用
self.navigationController.interactivePopGestureRecognizer.delegate = self;
然后实施
斯威夫特
extension MyViewController:UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool
return true
Objective-C
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
return YES;
【讨论】:
您能否指出任何提到与“滑动返回”功能相关的苹果文档? developer.apple.com/reference/uikit/uinavigationcontroller/… 不幸的是,如果您在推送动画期间或在根视图控制器上滑动,这将冻结。我在这里发布了一个修复版本:***.com/a/43433530/308315 这行得通!但为什么?谁能提供更多解释?为什么要求这个手势识别器通过另一个手势识别器失败以某种方式神奇地使它真正识别手势?? 仍在拯救生命! ?【参考方案4】:它适用于我Swift 3:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool
return true
在 ViewDidLoad 中:
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
【讨论】:
另外,如果你有一个 UIViewController,你也应该从 UIGestureRecognizerDelegate 继承。 这对我有用,但我不需要使用.isEnabled
行。
不幸的是,如果您在推送动画期间或在根视图控制器上滑动,这将冻结。我在这里发布了一个修复版本:***.com/a/43433530/308315【参考方案5】:
这是在 iOS 10、Swift 3 中启用/禁用滑动到弹出视图控制器的最佳方法:
对于第一个屏幕 [您要禁用滑动手势的位置]:
class SignUpViewController : UIViewController,UIGestureRecognizerDelegate
//MARK: - View initializers
override func viewDidLoad()
super.viewDidLoad()
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
swipeToPop()
override func viewWillDisappear(_ animated: Bool)
super.viewWillDisappear(animated)
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
func swipeToPop()
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true;
self.navigationController?.interactivePopGestureRecognizer?.delegate = self;
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
if gestureRecognizer == self.navigationController?.interactivePopGestureRecognizer
return false
return true
对于中间屏幕[您要启用滑动手势的位置]:
class FriendListViewController : UIViewController
//MARK: - View initializers
override func viewDidLoad()
super.viewDidLoad()
swipeToPop()
func swipeToPop()
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true;
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil;
【讨论】:
不幸的是,如果您在推送动画期间或在根视图控制器上滑动,这将冻结。我在这里发布了一个修复版本:***.com/a/43433530/308315 @iwasrobbed 我在根视图控制器上检查了上面的代码,它工作正常我不知道为什么它最终失败了。感谢更新版本 这是完美的答案,@iwasrobbed 你的代码不起作用 @NitinBhatt,cn 如果答案对您有用,请给我 +1。谢谢【参考方案6】:我不需要为它添加gestureRecognizer 函数。我在 viewDidLoad 中添加以下代码块就足够了:
override func viewDidLoad()
super.viewDidLoad()
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
【讨论】:
这适用于我在 iOS 13.3 上!delegate = nil
部分是关键。
检查这个答案以避免错误:***.com/a/61668408/1887537【参考方案7】:
斯威夫特 3:
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
return true
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool
return (otherGestureRecognizer is UIScreenEdgePanGestureRecognizer)
【讨论】:
不幸的是,如果您在推送动画期间或在根视图控制器上滑动,这将冻结。我在这里发布了一个修复版本:***.com/a/43433530/308315【参考方案8】:在 Swift 中,您可以执行以下代码
import UIKit
extension UINavigationController: UIGestureRecognizerDelegate
open override func viewDidLoad()
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
return viewControllers.count > 1
上面的代码有助于快速返回到之前的控制器,如 Facebook、Twitter。
【讨论】:
【参考方案9】:如果您希望在您的应用中随处可见这种行为,并且不想向单个 viewDidAppear
等添加任何内容,那么您应该创建一个子类
class QFNavigationController:UINavigationController, UIGestureRecognizerDelegate, UINavigationControllerDelegate
override func viewDidLoad()
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
delegate = self
override func pushViewController(_ viewController: UIViewController, animated: Bool)
super.pushViewController(viewController, animated: animated)
interactivePopGestureRecognizer?.isEnabled = false
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)
interactivePopGestureRecognizer?.isEnabled = true
// IMPORTANT: without this if you attempt swipe on
// first view controller you may be unable to push the next one
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
return viewControllers.count > 1
现在,无论何时使用QFNavigationController
,您都会获得所需的体验。
【讨论】:
【参考方案10】:设置自定义后退按钮禁用后滑功能。
保留它的最好办法是继承UINavigationViewController
并将自己设置为interactivePopGestureRecognizer
委托;然后您可以从gestureRecognizerShouldBegin
返回 YES 以允许向后滑动。
例如,这是在AHKNavigationController中完成的
这里有一个 Swift 版本:https://***.com/a/43433530/308315
【讨论】:
导航栏隐藏时也弹出禁用【参考方案11】:This 回答,但支持故事板。
class SwipeNavigationController: UINavigationController
// MARK: - Lifecycle
override init(rootViewController: UIViewController)
super.init(rootViewController: rootViewController)
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.setup()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
self.setup()
private func setup()
delegate = self
override func viewDidLoad()
super.viewDidLoad()
// This needs to be in here, not in init
interactivePopGestureRecognizer?.delegate = self
deinit
delegate = nil
interactivePopGestureRecognizer?.delegate = nil
// MARK: - Overrides
override func pushViewController(_ viewController: UIViewController, animated: Bool)
duringPushAnimation = true
super.pushViewController(viewController, animated: animated)
// MARK: - Private Properties
fileprivate var duringPushAnimation = false
【讨论】:
【参考方案12】:Swift 5,在 viewDidLoad 方法中只添加这两个:
override func viewDidLoad()
super.viewDidLoad()
navigationController?.interactivePopGestureRecognizer?.delegate = self
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
【讨论】:
【参考方案13】:我们都在努力解决 some old bugs 的问题,因为它是“设计使然”,因此可能无法修复。我在尝试取消interactivePopGestureRecognizer
的代表时遇到了@iwasrobbed 在其他地方描述的冻结问题,这似乎应该有效。如果您希望滑动行为重新考虑使用您可以自定义的backBarButtonItem
。
当UINavigationBar
被隐藏时,我也遇到了interactivePopGestureRecognizer
不工作。如果您担心隐藏导航栏,请在实施错误解决方法之前重新考虑您的设计。
【讨论】:
【参考方案14】:大多数答案都与代码相关。但我会给你一个在 Storyboard 上工作的。是的!你没看错。
点击主UINavigationController
并导航到它的Identity Inspector
选项卡。
在User Defined Runtime Attributes
下,将名为interactivePopGestureRecognizer.enabled
的单个运行时属性设置为true
。或者以图形方式,您必须启用复选框,如下图所示。
就是这样。你可以走了。您的后背手势将像一直存在一样起作用。
【讨论】:
【参考方案15】:我在启用和禁用滑动交互以弹出视图控制器时遇到问题。
我有一个基本导航控制器,我的应用流程就像推送 Splash VC,推送 Main VC,然后推送 Some VC。
我想滑动以从 Some VC 返回到 Main VC。还要禁用滑动以防止从主 VC 回到飞溅。
经过下面的一些尝试对我有用。
-
在 Main VC 中编写一个扩展来禁用滑动
extension MainViewController : UIGestureRecognizerDelegate
func disableSwipeToPop()
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
if gestureRecognizer == self.navigationController?.interactivePopGestureRecognizer
return false
return true
-
在 Main VC 的 viewDidAppear 上调用 disableSwipeToPop() 方法
override func viewDidAppear(_ animated: Bool)
super.viewDidAppear(animated)
self.disableSwipeToPop()
-
在 Some VC 中编写扩展,以启用滑动弹出 Some VC
extension SomeViewController
func enableSwipeToPop()
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
-
在某些 VC 的 viewDidLoad 上调用 enableSwipeToPop() 方法
override func viewDidLoad()
super.viewDidLoad()
self.enableSwipeToPop()
就是这样。此外,如果您尝试在 viewWillAppear 上禁用滑动,当用户停止滑动以取消操作时,您可能会失去再次滑动的能力。
【讨论】:
【参考方案16】:我创建了以下 Swift 5+
UIViewController
扩展程序,以便更轻松地在您需要的每个屏幕上添加/删除交互式弹出手势。
注意:
在每个具有自定义后退按钮的屏幕上添加enableInteractivePopGesture()
在viewDidAppear
上为导航控制器的根屏幕添加disableInteractivePopGesture()
,以防止此处提到的一些答案出现向后滑动问题
还可以在您不希望有返回按钮和滑动返回手势的推送屏幕上添加disableInteractivePopGesture()
extension UIViewController: UIGestureRecognizerDelegate
func disableInteractivePopGesture()
navigationItem.hidesBackButton = true
navigationController?.interactivePopGestureRecognizer?.delegate = self
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
func enableInteractivePopGesture()
navigationController?.interactivePopGestureRecognizer?.delegate = self
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
【讨论】:
【参考方案17】:对于那些仍然遇到此问题的人,请尝试如下将两行分开。
override func viewDidLoad()
self.navigationController!.interactivePopGestureRecognizer!.delegate = self
...
override func viewWillAppear(_ animated: Bool)
self.navigationController!.interactivePopGestureRecognizer!.isEnabled = true
...
显然,在我的应用中,
interactivePopGestureRecognizer!.isEnabled
在视图显示之前由于某种原因被重置为false
。
【讨论】:
以上是关于设置leftBarButtonItem后如何在UINavigationController中启用后退/左滑手势?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 leftBarButtonItem 上的 pushViewController 上更改动画方向?
ios:leftBarButtonItem.target 和 .action