You can store a reference to your bar button item or button and recreate the menu each time any state changes that affects the available actions in the menu. menu
is a settable property so it can be changed any time after the button is created. You can also get the current menu and replace its children like so: button.menu = button.menu?.replacingChildren([])
For scenarios where you are not informed when the state changes for example, you really need to be able to update the menu right before it appears. There is a UIDeferredMenuElement
API which allows the action(s) to be generated dynamically. It's a block where you call a completion handler providing an array of UIMenuElement
. A placeholder with loading UI is added by the system and is replaced once you call the completion handler, so it supports asynchronous determination of menu items. However, this block is only called once and then it is cached and reused so this doesn't do what we need for this scenario. iOS 15 added a new uncached provider API which behaves the same way except the block is invoked every time the element is displayed, which is exactly what we need for this scenario.
barButtonItem.menu = UIMenu(children: [
UIDeferredMenuElement.uncached { [weak self] completion in
var actions = [UIMenuElement]()
if self?.includeTestAction == true {
actions.append(UIAction(title: "Test Action") { [weak self] action in
self?.performTestAction()
})
}
completion(actions)
}
])
Before this API existed, I did find for UIButton
you can change the menu when the user touches down via target/action like so: button.addTarget(self, action: #selector(buttonTouchedDown(_:)), for: .touchDown)
. This worked only if showsMenuAsPrimaryAction
was false so they had to long press to open the menu. I didn't find a solution for UIBarButtonItem
, but you could use a UIButton
as a custom view.
UIBarButtonItem
. I do not find a way, to detectUIBarButtonItem
press, then build the menu dynamically and show the dynamic menu. – Lebanon