How to trigger UIContextMenuInteraction context menu programmatically?

The context menu is, by design, automatically shown by the system when an appropriate gesture (a force touch or a long press) occurs. You can't manually show it.

From docs:

A context menu interaction object tracks Force Touch gestures on devices that support 3D Touch, and long-press gestures on devices that don't support it.

UIKit manages all menu-related interactions and reports the selected action, if any, back to your app.

UPDATE:

Although it's still not possible to manually show the context menu in iOS 14, it's now possible to show the UIMenu we create for the context menu as a pull-down menu. Check @Lobo's answer for how to do that on iOS 14.


Using a private API can do this job.

@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)
}

iOS 14

iOS 14 (first Beta) now supports the desired functionality. With the following code tapping on the UIBarButtonItem will display the menu immediately (also avoiding the blurred background that results from calling 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
}

The functionality is achieved by not providing the primaryAction.

You can achieve the same effect using an UIButton. In that case you will need to set

button.showsMenuAsPrimaryAction = true

The full code for an UIButton could look like this:

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
}