I had no trouble implementing a pop-up button that lets a user select from a mutually exclusive list of options. This is covered in the Pop-up buttons section of the HIG.
Now I want something similar but to allow the user to select any number of options from the list. The "Pop-up buttons" page in the HIG states:
Use a pull-down button instead if you need to: [...] Let people select multiple items
But the Pull-down buttons page of the HIG makes no mention of how to support multiple selection.
Here's what I tried so far. I start with the pop-up button code (copy and paste into an iOS Swift Playground to play along):
import UIKit
import PlaygroundSupport
class MyVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let items = [ "Option 1", "Option 2", "Option 3", "Option 4" ]
let actions: [UIAction] = items.map {
let action = UIAction(title: $0) { action in
print("Selected \(action.title)")
}
return action
}
let menu = UIMenu(children: actions)
var buttonConfig = UIButton.Configuration.gray()
let button = UIButton(configuration: buttonConfig)
button.menu = menu
button.showsMenuAsPrimaryAction = true
button.changesSelectionAsPrimaryAction = true
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
])
}
}
PlaygroundPage.current.liveView = MyVC()
Then update the code to make it a pull-down button. First, disable the changesSelectionAsPrimaryAction
property of the button.
button.changesSelectionAsPrimaryAction = false
Then give the button a title so it appears as more than a tiny little square.
buttonConfig.title = "Select Items"
Now we have a button that shows a menu when it's tapped. But now there are no checkmarks and selecting a menu doesn't result in any checkmark. So here I thought I would update the handler block of the UIAction
to toggle the action's state
.
let action = UIAction(title: $0) { action in
print("Selected \(action.title)")
action.state = action.state == .on ? .off : .on
}
But now when you tap on a menu item the code crashes with an exception. When running in a real iOS app (not a Playground), the error is:
2023-05-21 10:40:56.038217-0600 ButtonMenu[63482:10716279] *** Assertion failure in -[_UIImmutableAction setState:], UIAction.m:387
2023-05-21 10:40:56.063676-0600 ButtonMenu[63482:10716279] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Action is immutable because it is a child of a menu'
Is it possible to implement a multiple select menu using UIButton
and UIMenu
?
If so, what piece am I missing?
If not, what component should be used for multiple selection? Ideally it would be great if the user could tap the button to bring up the menu, select multiple items in the menu, then tap the button again to dismiss the menu.