iOS14及以上版本中,在UINavigationItem的backBarButtonItem
上轻按并长按,将呈现完整的导航堆栈。然后用户可以弹出堆栈中的任何一个点,而以前用户只能轻按此项以弹出堆栈中的一项。
是否可以禁用此功能?UIBarButtonItem有一个名为menu
的新属性,但似乎为空,尽管在按住按钮时显示菜单。这让我相信这可能是无法更改的特殊行为,但也许我忽略了什么。
iOS14及以上版本中,在UINavigationItem的backBarButtonItem
上轻按并长按,将呈现完整的导航堆栈。然后用户可以弹出堆栈中的任何一个点,而以前用户只能轻按此项以弹出堆栈中的一项。
是否可以禁用此功能?UIBarButtonItem有一个名为menu
的新属性,但似乎为空,尽管在按住按钮时显示菜单。这让我相信这可能是无法更改的特殊行为,但也许我忽略了什么。
可以通过子类化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属性,可以自定义视图控制器的返回按钮,并控制菜单。我所看到的唯一问题是失去了系统按钮的本地化(翻译)“Back”字符串。
如果您希望禁用菜单成为默认行为,则可以在UINavigationController子类中实现此功能,并符合UINavigationControllerDelegate:
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
}
}
这基本上与Andrei Marincas的子类和设置解决方案相同。
但每次推送一个视图控制器时设置backBarButtonItem会导致返回按钮出现烦人的过渡效果。
因此,我将UIBarButtonItem.menu
的默认setter方法替换为一个无操作的代码块,它不会对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
}
}
以下是https://stackoverflow.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
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?) {
}
}
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
}
}
}
UIViewController.swizzle()
从这个答案中找到了使用sizzling的想法:https://stackoverflow.com/a/64713022/8817327