iosuikituibarbuttonitemuimenu

How to conditionally show either a UIMenu or an alert from a UIBarButtonItem?


I want to present a menu from a UIBarButtonItem, but only if a runtime check succeeds when the button is tapped, otherwise show an alert.

Quick background. I had some older code (pre-UIMenu days) that handled the UIBarButtonItem with a target/action that would perform the check and then either show an alert or present an action sheet.

I'm trying to update that code to use a UIMenu instead of an action sheet (UIAlertController). I know how to create the UIBarButtonItem with a UIMenu. That's easy to implement.

What I can't find in any APIs or in any searching here on SO, is how to manually display a UIMenu.

Here's a rough example of my code that directly shows a menu from the UIBarButtonItem:

btnAdd = UIBarButtonItem(systemItem: .add, menu: UIMenu(children: [
    // An array of UIAction instances for each menu item
]))

That code works just fine but I need to change it so the menu only appears under the right condition. I'm thinking of something like the following but I don't know how to write the line of code that manually displays a UIMenu.

btnAdd = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addAction))

...

@objc func addAction(_ sender: UIBarButtonItem) {
    if someRuntimeCondition == true {
        let menu = UIMenu(children: [
            // An array of UIAction instances for each menu item
        ])

        ??? // How to display menu from sender?
    } else {
        // Create and display an alert
    }
}

I feel like I'm missing something simple and obvious but I just don't see it.

I've reviewed the documentation for UIMenu, UIBarButtonItem, UIContextMenuInteraction, and UIMenuController (deprecated). None of these seem to provide a way to manually display a menu from a UIBarButtonItem. I've also looked at a couple of Apple's sample apps.

Any solution needs to work with iOS 15.0+.


Solution

  • I was seconds away from clicking Submit on my question when I found a tricky solution using UIDeferredMenuElement.uncached. This doesn't answer the question of how to manually display a UIMenu from a UIBarButtonItem but this does achieve my ultimate goal of conditionally showing either an alert or a menu when the UIBarButtonItem is tapped.

    btnAdd = UIBarButtonItem(systemItem: .add, menu: UIMenu(children: [
        UIDeferredMenuElement.uncached({ [weak self] completion in
            if someRuntimeCondition == true {
                completion([]) // send back an empty list of menu items
                // Show UIAlertController
            } else {
                let children = [
                    // An array of UIAction instances for each menu item
                ]
                completion(children) // send back the proper menu items
            }
        })
    ]))