是否可以在 iOS 14+ 中禁用后退导航菜单?
Posted
技术标签:
【中文标题】是否可以在 iOS 14+ 中禁用后退导航菜单?【英文标题】:Is it possible to disable the back navigation menu in iOS 14+? 【发布时间】:2020-07-13 20:13:09 【问题描述】:在 ios 14+ 中,点击并按住 UINavigationItem 的 backBarButtonItem
将显示完整的导航堆栈。然后用户可以弹出到堆栈中的任何点,而以前用户所能做的只是点击该项目以弹出堆栈中的一项。
是否可以禁用此功能? UIBarButtonItem 有一个名为menu
的新属性,但尽管在按住按钮时显示菜单,但它似乎为 nil。这让我相信这可能是无法改变的特殊行为,但也许我忽略了一些东西。
【问题讨论】:
***.com/a/49267846/4759154 似乎证实了我的怀疑,即这是由私有 API 控制的,但也许有办法...... 你做到了吗? 你看到/尝试***.com/questions/62158125/…了吗?我刚刚将此代码添加到我的项目中,它可以按我的意愿工作.... 【参考方案1】:可以通过继承 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
然后您可以按照自己喜欢的方式在视图控制器中配置后退按钮,但使用 BackBarButtonItem 而不是 UIBarButtonItem。
let backButton = BackBarButtonItem(title: "BACK", style: .plain, target: nil, action: nil)
navigationItem.backBarButtonItem = backButton
这是首选,因为您只在视图控制器的导航项中设置了一次 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
【讨论】:
聪明,但你也必须在每个视图控制器上都这样做。 @matt 有一些方法可以在堆栈中的每个视图控制器的 navigationItem 上设置一个 backBarButtonItem,例如通过实现 UINavigationControllerDelegate 的 navigationController:didShow: 委托方法。您不必在每个视图控制器中都这样做。但是要依靠 leftBarButtonItem 来为每个可能被推送的视图控制器执行 popViewController,这是有风险的。如果您有兴趣,这里有更完整的答案:developer.apple.com/forums/thread/… @matt 是的,您必须在每个将被推送的 UIViewController 对象上设置一个自定义后退按钮,以便控制菜单。但是以编程方式,您可以在一个地方实现它。我看到的唯一问题是您丢失了“Back”字符串本地化。我没有看到任何其他正统的禁用菜单的方法。系统后退按钮不公开。 虽然这是一个无用的功能,但我讨厌它给用户带来混乱的泡沫 这个 3D touch 可以在应用中完全禁用吗?【参考方案2】:运行时调配是最终的解决方案。
和Andrei Marincas的subclass and set solution的思路基本一样。
但是每次按下视图控制器时设置 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
粘贴到任何地方。在您的AppDelegate
中调用它:
@main
class AppDelegate: UIResponder
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool
// ......
Runtime.swizzle()
return true
【讨论】:
【参考方案3】:这里也复制https://***.com/a/64386494/95309的答案。
如果您看到“空”菜单是因为您当前将backButtonTitle
设置为空字符串,或者将backBarButtonItem
设置为空标题以删除后退按钮标题,您应该改为从 iOS 14 及更高版本将 backButtonDisplayMode
设置为 minimal
。
if #available(iOS 14.0, *)
navigationItem.backButtonDisplayMode = .minimal
else
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
https://developer.apple.com/documentation/uikit/uinavigationitem/3656350-backbuttondisplaymode
【讨论】:
它不会禁用,但可以防止它全部为空,同时保持干净的后退按钮。【参考方案4】:在 didFinishLaunchingWithOptions 中调用 UIBarButtonItem.fix_classInit()。 方法交换的目的是在菜单设置器中什么都不做。
func swizzlingClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector)
guard let originalMethod = class_getInstanceMethod(forClass, originalSelector), let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) else
return
if class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
else
method_exchangeImplementations(originalMethod, swizzledMethod)
extension UIBarButtonItem
public static func fix_classInit()
if #available(iOS 14.0, *)
swizzlingClass(UIBarButtonItem.self, originalSelector: #selector(setter: UIBarButtonItem.menu), swizzledSelector: #selector(fix_setMenu(menu:)))
@available(iOS 14.0, *)
@objc func fix_setMenu(menu: UIMenu?)
【讨论】:
一般来说,如果答案包含对代码的用途的解释,以及为什么在不介绍其他人的情况下解决问题的原因,答案会更有帮助。【参考方案5】:Andrei Marincas 的解决方案对我有用。但是,在每个根导航控制器上设置自定义 UIBarButtonItem 是很烦人的。在某些情况下,我发现设置自定义栏按钮项不适用于所有子类(可能如果子 vc 对故事板的导航栏进行了一些修改?)。所以我使用 swizzling 技术在每个 ViewDidLoad 上添加自定义的后退栏按钮项。
import UIKit
private let swizzling: (UIViewController.Type, Selector, Selector) -> Void = forClass, originalSelector, swizzledSelector in
if let originalMethod = class_getInstanceMethod(forClass, originalSelector), let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
let didAddMethod = class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod
class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
else
method_exchangeImplementations(originalMethod, swizzledMethod)
extension UIViewController
static func swizzle()
let originalSelector1 = #selector(viewDidLoad)
let swizzledSelector1 = #selector(swizzled_viewDidLoad)
swizzling(UIViewController.self, originalSelector1, swizzledSelector1)
@objc open func swizzled_viewDidLoad()
if let _ = navigationController
let backButton = BackBarButtonItem(title: " ", style: .plain, target: nil, action: nil) // Set any title you'd like, I needed to show only the back icon.
navigationItem.backBarButtonItem = backButton
swizzled_viewDidLoad()
// From Andrei's answer
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
并在 application(_:didFinishLaunchingWithOptions:) 调用
UIViewController.swizzle()
从这个答案中找到了使用 sizzling 的想法:https://***.com/a/64713022/8817327
【讨论】:
以上是关于是否可以在 iOS 14+ 中禁用后退导航菜单?的主要内容,如果未能解决你的问题,请参考以下文章