Контекстное меню iOS 14 из UIView (не из UIButton или UIBarButtonItem)
Есть простой способ представить контекстное меню в iOS 13/14 через
UIContextMenuInteraction
:
anyUIView.addInteraction(UIContextMenuInteraction(delegate: self))
Проблема для меня в том, что он размывает весь пользовательский интерфейс. Кроме того, это вызывается только через долгое нажатие / тактильное касание.
Если я не хочу размытия, есть меню действий. Как показано здесь https://developer.apple.com/documentation/uikit/menus_and_shortcuts/adopting_menus_and_uiactions_in_your_user_interface
Кажется, что это присутствует без размытия, но это только кажется прикрепленным к
UIButton
или
UIBarButtonItem
.
let infoButton = UIButton()
infoButton.showsMenuAsPrimaryAction = true
infoButton.menu = UIMenu(options: .displayInline, children: [])
infoButton.addAction(UIAction { [weak infoButton] (action) in
infoButton?.menu = infoButton?.menu?.replacingChildren([new items go here...])
}, for: .menuActionTriggered)
Есть ли способ прикрепить контекстное меню к UIView, которое вызывается при длительном нажатии и не имеет размытия?
2 ответа
После некоторых экспериментов мне удалось удалить затемняющее размытие, вот так. Вам понадобится служебный метод:
extension UIView {
func subviews<T:UIView>(ofType WhatType:T.Type,
recursing:Bool = true) -> [T] {
var result = self.subviews.compactMap {$0 as? T}
guard recursing else { return result }
for sub in self.subviews {
result.append(contentsOf: sub.subviews(ofType:WhatType))
}
return result
}
}
Теперь мы используем метод делегата взаимодействия с контекстным меню, чтобы найти UIVisualEffectView, который отвечает за размытие, и устранить его:
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willDisplayMenuFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
DispatchQueue.main.async {
let v = self.view.window!.subviews(ofType:UIVisualEffectView.self)
if let v = v.first {
v.alpha = 0
}
}
}
Типичный результат:
К сожалению, теперь за меню совсем нет тени, но это лучше, чем большое размытие.
И, конечно же, это все еще долгое нажатие. Я сомневаюсь, что с этим можно что-нибудь сделать! Если бы это был обычный UILongPressGestureRecognizer, вы, вероятно, могли бы найти его и сократить его
minimumPressDuration
, но это не так; вы должны подчиняться правилам дорожного движения UIContextMenuInteraction.
Однако, сказав все , что я могу думать о гораздо лучший способ сделать это, если это возможно: сделать это UIView быть UIControl! Теперь он ведет себя как UIControl. Так например:
class MyControl : UIControl {
override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
let config = UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { _ in
let act = UIAction(title: "Red") { action in }
let act2 = UIAction(title: "Green") { action in }
let act3 = UIAction(title: "Blue") { action in }
let men = UIMenu(children: [act, act2, act3])
return men
})
return config
}
}
А также:
let v = MyControl()
v.isContextMenuInteractionEnabled = true
v.showsMenuAsPrimaryAction = true
v.frame = CGRect(x: 100, y: 100, width: 200, height: 100)
v.backgroundColor = .red
self.view.addSubview(v)
В результате простым касанием вызывается меню, которое выглядит следующим образом:
Так что, если вам удастся избежать такого подхода, я думаю, он будет намного лучше.
Я могу только продолжить ответ Мэтта - использовать UIControl намного проще. Хотя родного нет
menu
свойство, есть простой способ, как облегчить
contextMenuInteraction
setup, просто создайте подкласс UIControl и передайте туда свое меню!
class MenuControl: UIControl {
var customMenu: UIMenu
// MARK: Initialization
init(menu: UIMenu) {
self.customMenu = menu
super.init(frame: .zero)
isContextMenuInteractionEnabled = true
showsMenuAsPrimaryAction = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: ContextMenu
override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [weak self] _ in
self?.customMenu
})
}
}
Затем вам нужно только предоставить UIMenu с UIActions следующим образом:
let control = MenuControl(menu: customMenu)