是否可以在 iOS 14+ 中禁用后退导航菜单?

mmd*_*080 9 uikit ios swift ios14

在 iOS 14+ 中,点击并按住backBarButtonItemUINavigationItem 的 将显示完整的导航堆栈。然后用户可以弹出堆栈中的任何点,而以前用户只能点击该项目以弹出堆栈中的一个项目。

是否可以禁用此功能?UIBarButtonItem 有一个名为 的新属性menu,但尽管按住按钮时显示菜单,但它似乎为零。这让我相信这可能是无法改变的特殊行为,但也许我忽略了一些东西。

And*_*cas 12

它可以通过继承 UIBarButtonItem 来完成。在 UIBarButtonItem 上将菜单设置为 nil 不起作用,但您可以覆盖菜单属性并首先阻止设置它。

class BackBarButtonItem: UIBarButtonItem {
    @available(iOS 14.0, *)
    override var menu: UIMenu? {
        set {
            // Don't set the menu here
            // super.menu = menu
        }
        get {
            return super.menu
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以按照你喜欢的方式在你的视图控制器中配置后退按钮,但是使用 BackBarButtonItem 而不是 UIBarButtonItem。

let backButton = BackBarButtonItem(title: "BACK", style: .plain, target: nil, action: nil)
navigationItem.backBarButtonItem = backButton
Run Code Online (Sandbox Code Playgroud)

这是首选,因为您只在视图控制器的导航项中设置 backBarButtonItem 一次,然后无论它要推送什么视图控制器,推送的控制器都会在导航栏上自动显示后退按钮。如果使用 leftBarButtonItem 而不是 backBarButtonItem,则必须在将被推送的每个视图控制器上设置它。

编辑:

长按出现的后退导航菜单是 UIBarButtonItem 的一个属性。视图控制器的后退按钮可以通过设置 navigationItem.backBarButtonItem 属性来自定义,这样我们就可以控制菜单。我看到的这种方法的唯一问题是丢失了系统按钮具有的“返回”字符串的本地化(翻译)。

如果您希望禁用菜单成为默认行为,您可以在一个地方实现这一点,在符合 UINavigationControllerDelegate 的 UINavigationController 子类中:

class NavigationController: UINavigationController, UINavigationControllerDelegate {
  init() {
    super.init(rootViewController: ViewController())
    delegate = self
  }
   
  func navigationController(_ navigationController: UINavigationController,
                            willShow viewController: UIViewController, animated: Bool) {
    let backButton = BackBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
    viewController.navigationItem.backBarButtonItem = backButton
  }
}
Run Code Online (Sandbox Code Playgroud)


iMo*_*Nya 5

运行时调配是最终的解决方案。

它与Andrei Marincas子类和集合解决方案的思想基本相同。

但是每次按下视图控制器时设置 backBarButtonItem 都会导致后退按钮上出现烦人的转换。

因此,我将 的默认设置器设置UIBarButtonItem.menu为不执行任何操作的代码块,这对 iOS 转换系统没有任何损害。

只需复制此代码即可:

enum Runtime {
    static func swizzle() {
        if #available(iOS 14.0, *) {
            exchange(
                #selector(setter: UIBarButtonItem.menu),
                with: #selector(setter: UIBarButtonItem.swizzledMenu),
                in: UIBarButtonItem.self
            )
        }
    }
    
    private static func exchange(
        _ selector1: Selector,
        with selector2: Selector,
        in cls: AnyClass
    ) {
        guard
            let method = class_getInstanceMethod(
                cls,
                selector1
            ),
            let swizzled = class_getInstanceMethod(
                cls,
                selector2
            )
        else {
            return
        }
        method_exchangeImplementations(method, swizzled)
    }
}

@available(iOS 14.0, *)
private extension UIBarButtonItem {
    @objc dynamic var swizzledMenu: UIMenu? {
        get {
            nil
        }
        set {
            
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

粘贴到任何地方。在您的中调用它AppDelegate


@main
class AppDelegate: UIResponder {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        // ......

        Runtime.swizzle()
        return true
    }
}
Run Code Online (Sandbox Code Playgroud)