如何以编程方式触发 UIContextMenuInteraction 上下文菜单?

问题描述 投票:0回答:4

我已将 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 contextmenu ios13 popupmenu ios14
4个回答
10
投票

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
}


8
投票

根据设计,当发生适当的手势(用力触摸或长按)时,系统会自动显示上下文菜单。您无法手动显示它。

来自文档

上下文菜单交互对象在支持 3D Touch 的设备上跟踪 Force Touch 手势,在不支持 3D Touch 的设备上跟踪长按 手势。

UIKit 管理所有与菜单相关的交互并将所选操作(如果有)报告回您的应用程序。

更新

虽然在 iOS 14 中仍然无法手动显示上下文菜单,但现在可以将我们为上下文菜单创建的

UIMenu
显示为 下拉菜单。请查看 @Lobo 的回答,了解如何在 iOS 14 上执行此操作。


5
投票

我找到了一种显示 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])
    }
}

在实践中,如果您不确定,最好通过类型找到实际的识别器,而不是使用绝对索引。

显示隐藏按钮的菜单:

enter image description here


4
投票

使用私有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)
}
© www.soinside.com 2019 - 2024. All rights reserved.