iOS 14 如何从 UIView 中弹出上下文菜单(而不是从 UIButton 或 UIBarButtonItem)?

7

通过 UIContextMenuInteraction,在 iOS 13/14 中呈现上下文菜单的方法非常简单:

anyUIView.addInteraction(UIContextMenuInteraction(delegate: self))

对我来说,这个问题在于它会模糊整个用户界面。此外,这只能通过长按/Haptic Touch 调用。
如果我不想要模糊效果,可以使用操作菜单。如下所示: https://developer.apple.com/documentation/uikit/menus_and_shortcuts/adopting_menus_and_uiactions_in_your_user_interface enter image description here 这似乎没有模糊效果,但似乎只能附加到UIButtonUIBarButtonItem上。
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个回答

8
经过一些尝试,我成功地移除了模糊效果,像这样。你需要一个实用方法:
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)

结果就是简单地敲击屏幕即可调用菜单,长这样:

enter image description here

如果您能使用这种方法,我认为它更加美观。

感谢你在UIControl方面的帮助,Matt!我很好奇为什么他们从UIControl中移除了"menu"属性,之前是可以访问的,对吧? - Ondřej Korol
@ashipma,您已经接受了一个答案,所以能否删除这个评论?谢谢。 - matt
@matt,你是我的英雄!我在互联网上搜索了很久,想找到一个代码片段来模仿UIButton在某些UIControl上的showsMenuAsPrimaryAction = true行为。谢谢你! - undefined

2

我只能跟进Matt的回答 - 使用UIControl会更容易。虽然没有本地的menu属性,但有一种简单的方法可以简化contextMenuInteraction的设置,只需创建一个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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接