【问题标题】:How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem?设置leftBarButtonItem后如何在UINavigationController中启用后退/左滑手势?
【发布时间】: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 谢谢!这意味着所有的屏幕滑动手势都是用于向后滑动,我认为这不是一个好主意。

标签: ios objective-c uinavigationcontroller uinavigationbar uinavigationitem


【解决方案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】:

你需要处理两种情况:

  1. 将新视图推送到堆栈时
  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 } 以用于故事板:)
  • 我添加了故事板支持:stackoverflow.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;
}

【讨论】:

  • 您能否指出任何提到与“滑动返回”功能相关的苹果文档?
  • 不幸的是,如果您在推送动画期间或在根视图控制器上滑动,这将冻结。我在这里发布了一个修复版本:stackoverflow.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 行。
  • 不幸的是,如果您在推送动画期间或在根视图控制器上滑动,这将冻结。我在这里发布了一个修复版本:stackoverflow.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;
} }

【讨论】:

  • 不幸的是,如果您在推送动画期间或在根视图控制器上滑动,这将冻结。我在这里发布了一个修复版本:stackoverflow.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
}

【讨论】:

【解决方案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)
}

【讨论】:

  • 不幸的是,如果您在推送动画期间或在根视图控制器上滑动,这将冻结。我在这里发布了一个修复版本:stackoverflow.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://stackoverflow.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 回到飞溅。

              经过下面的一些尝试对我有用。

              1. 在 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
                  }
              }
              
              1. 在 Main VC 的 viewDidAppear 上调用 disableSwipeToPop() 方法
              override func viewDidAppear(_ animated: Bool) {
                      super.viewDidAppear(animated)
                      self.disableSwipeToPop()
              }
              
              1. 在 Some VC 中编写扩展,以启用滑动弹出 Some VC
              extension SomeViewController{
                  
                  func enableSwipeToPop() {
                      self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
                      self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
                  }
                  
              }
              
              1. 在某些 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

                  【讨论】:

                    猜你喜欢
                    • 2014-06-12
                    • 2013-06-17
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-10-19
                    • 2016-11-24
                    • 1970-01-01
                    • 2020-03-03
                    • 2021-08-21
                    相关资源
                    最近更新 更多