Search code examples
iosswiftuilabeluimenucontrollerlong-press

UILabel - Long press gesture for menu item of UIMenuController


I need to handle long press action/gesture on UILable, in an entire app, and it should show a menu like this, with custom menu options:

enter image description here

As per apple interface guideline a text field, a text view, a web view, and an image view can only enable this menu.

Is it feasible to add such action in UILabel for the entire app and open custom menu by adding own menu options, with existing?


Solution

  • Here is a UILabel subclass that handles long pressing to show the UIMenuController. You can also add more actions the the menu controller for use case.

    import UIKit
    
    class MenuLabel: UILabel {
    
        override var canBecomeFirstResponder: Bool {
            return true
        }
    
        // MARK: - Init
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            commonInit()
        }
    
        private func commonInit() {
            isUserInteractionEnabled = true
            addGestureRecognizer(
                UILongPressGestureRecognizer(
                    target: self,
                    action: #selector(handleLongPressed(_:))
                )
            )
        }
    
        // MARK: - Actions
    
        internal func handleLongPressed(_ gesture: UILongPressGestureRecognizer) {
            guard let gestureView = gesture.view, let superView = gestureView.superview else {
                return
            }
    
            let menuController = UIMenuController.shared
    
            guard !menuController.isMenuVisible, gestureView.canBecomeFirstResponder else {
                return
            }
    
            gestureView.becomeFirstResponder()
    
            menuController.menuItems = [
                UIMenuItem(
                    title: "Custom Item",
                    action: #selector(handleCustomAction(_:))
                ),
                UIMenuItem(
                    title: "Copy",
                    action: #selector(handleCopyAction(_:))
                )
            ]
    
            menuController.setTargetRect(gestureView.frame, in: superView)
            menuController.setMenuVisible(true, animated: true)
        }
    
        internal func handleCustomAction(_ controller: UIMenuController) {
            print("Custom action!")
        }
    
        internal func handleCopyAction(_ controller: UIMenuController) {
            UIPasteboard.general.string = text ?? ""
        }
    
    }
    

    Key things to takeaway from this are:

    • making sure the label overrides canBecomeFirstResponder
    • isUserInteractionEnabled set to true
    • calling gestureView.becomeFirstResponder() in the long press handler

    You can add this label to Interface Builder or create it in code.

    Hope this helps!