【问题标题】:Replace back button but keeping swipe to navigate back替换后退按钮,但保持滑动以向后导航
【发布时间】:2025-12-14 18:25:01
【问题描述】:

首先,我注意到有一个similar question。但是,我想问一下 Swift 中的解决方案。这是我的代码:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Button", style: .Plain, target: self, action: nil)
}

我想替换后退按钮,但保持滑动以供用户向后导航。但是,这会禁用滑动。我无法在上面的链接中获得接受的答案和建议的答案。这是我试图翻译成 Swift 的内容。

let appearanceNavigationBar = UINavigationBar.appearance()
appearanceNavigationBar.backIndicatorImage = UIImage(named: "back")
appearanceNavigationBar.backIndicatorTransitionMaskImage = UIImage(named: "back")
appearanceNavigationBar.tintColor = UIColor.whiteColor()

我正在使用 Xcode 8.0 beta、Swift 2.3 并在 iOS 10.0 中进行测试。任何帮助将不胜感激。

【问题讨论】:

  • 也许你可以看看这个属性:interactivePopGestureRecognizer
  • @Siam Setting interactivePopGestureRecognizer.enabled = true 什么都不做。重新分配interactivePopGestureRecognizer.delegate 会导致很多问题。 (来自建议的答案)

标签: ios swift uinavigationcontroller uinavigationbar uinavigationitem


【解决方案1】:

我用过这个,效果很好:

self.navigationController.interactivePopGestureRecognizer.delegate = nil;

【讨论】:

  • 这行得通。您介意解释一下背后的原因吗?
  • 我还是不知道为什么。我刚刚提到了这个luugiathuy.com/2013/11/…
【解决方案2】:

接受的答案不完整。如果您配置了故事板转场,并且您在第一个视图控制器上尝试“向后滑动”手势,您可能无法触发转场。

考虑所有边缘情况的最佳解决方案是:

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

【讨论】:

    【解决方案3】:

    this answer 中所述将委托设置为nil 会导致iOS 11 中出现一些讨厌的错误,例如this one

    但是一般来说,存根委托的想法是好的。比如可以写如下代码

    final class NavigationController: UINavigationController {
        private var customDelegate: InteractivePopGestureRecognizerDelegate?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            customDelegate = InteractivePopGestureRecognizerDelegate(
                originalDelegate: interactivePopGestureRecognizer?.delegate,
                navigationController: self
            ) 
            interactivePopGestureRecognizer?.delegate = customDelegate
        }
    }
    

    然后您可以使用自定义委托来解决问题的根本原因

    final class InteractivePopGestureRecognizerDelegate:
        NSObject,
        UIGestureRecognizerDelegate
    {
        private weak var originalDelegate: UIGestureRecognizerDelegate?
        private weak var navigationController: UINavigationController?
    
        init(
            originalDelegate: UIGestureRecognizerDelegate?,
            navigationController: UINavigationController)
        {
            self.originalDelegate = originalDelegate
            self.navigationController = navigationController
        }
    
        func gestureRecognizer(
            _ gestureRecognizer: UIGestureRecognizer,
            shouldReceive touch: UITouch)
            -> Bool
        {
            if let originalDelegate = originalDelegate,
                let result = originalDelegate.gestureRecognizer?(
                    gestureRecognizer,
                    shouldReceive: touch)
            {
                if !result {
                    // Your interactive pop gesture got cancelled here
    
                    // Perform sanity check of pop possibility
                    if (navigationController?.viewControllers.count ?? 0) < 2 {
                        return result
                    }
    
                    if navigationController?.navigationBar.isHidden == true {
                        // Return true here if you want to swipe even without navigation bar
                        return result
                    }
    
                    // Enable pop gesture
                    return true
                }
            } else {
                return true
            }
        }
    
        ...
        // Proxy all other `UIGestureRecognizerDelegate` methods to the originalDelegate here
        ...
    }
    

    当然,这有点棘手,将来可能无法使用(就像其他所有黑客一样)

    【讨论】:

      【解决方案4】:

      好的,所以 3 年过去了,您可能不再需要解决此问题的方法了:) 但也许这会帮助其他人解决此问题。这实际上非常简单:您只需要配置后退按钮,它将从第一个视图控制器显示在第二个视图控制器上。这是一个例子:

      class FirstViewController: UIViewController {
        override func viewDidLoad() {
          let backButton = UIBarButtonItem(image: UIImage(systemName: "chevron.left.circle.fill"), style: .done, target: nil, action: nil)
          navigationItem.backBarButtonItem = backButton
        }
      
        func presentSecondViewController() {
          navigationController?.pushViewController(SecondViewController(), animated: true)
        }
      }
      

      以这种方式配置 FirstViewController 将允许您在 SecondViewController 上显示自定义后退按钮并保持向后滑动手势。

      【讨论】: