Как изменить UIMenu для UIBarButtonItem и UIButton
I OS 14 добавляет возможность отображать меню при касании или долгом нажатии UIBarButtonItem или UIButton, например:
let menu = UIMenu(children: [UIAction(title: "Action", image: nil) { action in
//do something
}])
button.menu = menu
barButtonItem = UIBarButtonItem(title: "Show Menu", image: nil, primaryAction: nil, menu: menu)
Это чаще всего заменяет листы действий (UIAlertController
с участием actionSheet
стиль). Очень часто бывает динамический лист действий, где действия только включены или могут быть отключены в зависимости от некоторого состояния в момент, когда пользователь нажимает кнопку. Но с этим API меню создается во время создания кнопки. Как вы можете изменить меню до его представления или иным образом сделать его динамическим, чтобы гарантировать, что соответствующие действия доступны и в правильном состоянии, когда оно появится?
5 ответов
Одно решение, которое я нашел, - это сохранить ссылку на элемент или кнопку кнопки на панели и воссоздавать меню каждый раз, когда любое изменение состояния влияет на доступные действия в меню. menu
является настраиваемым свойством, поэтому его можно изменить в любое время после создания кнопки. Вы также можете получить текущее меню и заменить его дочерние элементы следующим образом:button.menu = button.menu?.replacingChildren([])
Для сценариев, когда вы не получаете информацию об изменении состояния, вам действительно нужно иметь возможность обновлять меню прямо перед его появлением. Для UIButton вы можете изменить меню, когда пользователь касается кнопки с помощью target/action следующим образом:button.addTarget(self, action: #selector(buttonTouchedDown(_:)), for: .touchDown)
. Я подтвердил, что это работает с бета-версией iOS 14, по крайней мере, в настоящее время, но только еслиshowsMenuAsPrimaryAction
ложно, поэтому им нужно долго нажимать, чтобы открыть меню. Я не нашел решения для UIBarButtonItem, но вы можете использовать UIButton в качестве настраиваемого представления для UIBarButtonItem.
Они не кажутся отличными решениями, обновятся, если я найду что-то получше.
После некоторого испытания я обнаружил, что вы можете изменить
UIButton
с
.menu
установив
menu
собственность
null
сначала установите новый
UIIMenu
вот пример кода, который я сделал
@IBOutlet weak var button: UIButton!
func generateMenu(max: Int, isRandom: Bool = false) -> UIMenu {
let n = isRandom ? Int.random(in: 1...max) : max
print("GENERATED MENU: \(n)")
let items = (0..<n).compactMap { i -> UIAction in
UIAction(
title: "Menu \(i)",
image: nil
) {[weak self] _ in
guard let self = self else { return }
self.button.menu = nil // HERE
self.button.menu = self.generateMenu(max: 10, isRandom: true)
print("Tap")
}
}
let m = UIMenu(
title: "Test", image: nil,
identifier: UIMenu.Identifier(rawValue: "Hello.menu"),
options: .displayInline, children: items)
return m
}
override func viewDidLoad() {
super.viewDidLoad()
button.menu = generateMenu(max: 10)
button.showsMenuAsPrimaryAction = true
}
Нашел решение для случая с
UIBarButtonItem
. Мое решение основано на решении Jordan H , но я столкнулся с ошибкой - мой метод обновления меню
regenerateContextMenu()
не вызывался каждый раз, когда появляется меню, и я получал неактуальные данные в меню. Поэтому я немного изменил код:
private lazy var threePointBttn: UIButton = {
$0.setImage(UIImage(systemName: "ellipsis"), for: .normal)
// pay attention on UIControl.Event in next line
$0.addTarget(self, action: #selector(regenerateContextMenu), for: .menuActionTriggered)
$0.showsMenuAsPrimaryAction = true
return $0
}(UIButton(type: .system))
override func viewDidLoad() {
super.viewDidLoad()
threePointBttn.menu = createContextMenu()
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: threePointBttn)
}
private func createContextMenu() -> UIMenu {
let action1 = UIAction(title:...
// ...
return UIMenu(title: "Some title", children: [action1, action2...])
}
@objc private func regenerateContextMenu() {
threePointBttn.menu = createContextMenu()
}
протестировано на iOS 14.7.1
Я добавляю расширение для UIMenu:
extension UIMenu {
/// rebuilds menu on every access
static func lazyMenu(builder: @escaping () -> UIMenu) -> UIMenu {
return UIMenu(children: [
UIDeferredMenuElement.uncached { completion in
let menu = builder()
completion([menu])
}
])
}
}
Тогда везде, где необходимо динамическое меню, используйте:
UIMenu.lazyMenu {
// will be called on every menu access
return UIMenu(options: .displayInline, children: myDynamicMenuItems)
}
Изменена версия Джордана Х , чтобы разделить задание и построить действие.
Это будет создавать меню на лету каждый раз, когда нажимается кнопка.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem?.menu = UIMenu(children: [
// build menu every time the button is tapped
UIDeferredMenuElement.uncached { [weak self] completion in
if let menu = self?.buildMenu() as? UIMenu {
completion([menu])
}
}
])
}
func buildMenu() -> UIMenu {
var actions: [UIMenuElement] = []
// build actions
UIAction(title: "Filter", image: UIImage(systemName: "line.3.horizontal.decrease.circle")) { _ in
self.filterTapped()
}
actions.append(filterAction)
return UIMenu(options: .displayInline, children: actions)
}