我已将 UIButton 设置为 UINavigationController 内 UIViewController 中的 rightBarButtonItem,并将 iOS13 上下文菜单与其关联。
长按按钮会按预期显示上下文菜单。
有没有办法通过点击按钮来显示上下文菜单(例如,通过为 .touchUpInside 事件添加目标)?
button/barButtonItem 设置如下:
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "plus"), for: .normal)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.rightBarButtonItem = barButton
let interaction = UIContextMenuInteraction(delegate: self)
button.addInteraction(interaction)
上下文菜单定义如下:
extension ViewController: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
let importAction = UIAction(title: "Import", image: UIImage(systemName: "folder")) { action in }
let createAction = UIAction(title: "Create", image: UIImage(systemName: "square.and.pencil")) { action in }
return UIMenu(title: "", children: [importAction, createAction])
}
}
}
iOS 14
iOS 14(第一个 Beta)现在支持所需的功能。使用以下代码,点击 UIBarButtonItem 将立即显示菜单(也避免了调用 UIContextMenuInteraction 导致的背景模糊):
override func viewDidLoad() {
super.viewDidLoad()
let importAction = UIAction(title: "Import", image: UIImage(systemName: "folder")) { action in }
let createAction = UIAction(title: "Create", image: UIImage(systemName: "square.and.pencil")) { action in }
let menuBarButton = UIBarButtonItem(
title: "Add",
image: UIImage(systemName:"plus"),
primaryAction: nil,
menu: UIMenu(title: "", children: [importAction, createAction])
)
self.navigationItem.rightBarButtonItem = menuBarButton
}
该功能是通过 not 提供
primaryAction
来实现的。
您可以使用 UIButton 达到相同的效果。在这种情况下,您需要设置
button.showsMenuAsPrimaryAction = true
UIButton 的完整代码可能如下所示:
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "plus"), for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
let importAction = UIAction(title: "Import", image: UIImage(systemName: "folder")) { action in }
let createAction = UIAction(title: "Create", image: UIImage(systemName: "square.and.pencil")) { action in }
let items = [importAction, createAction]
button.menu = UIMenu(title: "Add", children: items)
button.showsMenuAsPrimaryAction = true
}
我找到了一种显示 UIMenu 无需点击并且不使用私有 api 的方法。
关键是实现系统通过相应的手势识别器来显示菜单,我们也可以这样做。
下面是在 1.0 秒后未单击按钮时显示 UIButton 菜单的示例。
即使 UIButton 隐藏,它也能工作。
这个方法可以扩展到其他视图。但是有一些限制。例如,当应用于 UIView 时,因为它是显示菜单的长按识别器,所以我们需要传递带有时间戳信息的真实 UIEvent。但是,如果没有私有 api 的帮助,我们无法更改 UIEvent.timestamp。在这种情况下,我们需要真正的交互,并将记录的真实 touchBegan UIEvent 传递给长按识别器(该事件可以重用)。
因此,为了在不需要长按的其他视图进行用户交互的情况下显示 UIMenu,我们可以声明一个隐藏的 UIButton(需要添加到某些视图)并将
showsMenuAsPrimaryAction
设置为 true
并实际显示 UIButton 的菜单。
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let btn = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
btn.backgroundColor = .lightGray
self.view.addSubview(btn)
// these two lines actually create the recognizers for showing the menu
btn.menu = getMenu()
btn.showsMenuAsPrimaryAction = true
for r in btn.gestureRecognizers! {
print(type(of: r))
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
print("show context menu without click")
// This is the gesture recognizer that shows the menu when it receive any touch began event
let r = btn.gestureRecognizers![2]
r.touchesBegan([], with: UIEvent())
}
}
private func getMenu() -> UIMenu {
let inspectAction =
UIAction(title: NSLocalizedString("InspectTitle", comment: ""),
image: UIImage(systemName: "arrow.up.square")) { action in
}
let deleteAction =
UIAction(title: NSLocalizedString("DeleteTitle", comment: ""),
image: UIImage(systemName: "trash"),
attributes: .destructive) { action in
}
return UIMenu(title: "", children: [inspectAction, deleteAction])
}
}
在实践中,如果您不确定,最好通过类型找到实际的识别器,而不是使用绝对索引。
显示隐藏按钮的菜单:
使用私有API可以完成这项工作。
@objc
func buttonTapped() {
// _presentMenuAtLocation:
guard let interaction = imageView.interactions.first,
let data = Data(base64Encoded: "X3ByZXNlbnRNZW51QXRMb2NhdGlvbjo="),
let str = String(data: data, encoding: .utf8)
else {
return
}
let selector = NSSelectorFromString(str)
guard interaction.responds(to: selector) else {
return
}
interaction.perform(selector, with: CGPoint.zero)
}