Does UILabel have any value to make it selectable?
Asked Answered
B

4

9

Does UILabel have any value that can be set in order to make it selectable?

I have a label that I want to be selectable, (long press and a copy btn shows up) kinda like in Safari.

Biancabiancha answered 11/10, 2016 at 23:51 Comment(0)
M
7

Yes, you need to implement a UIMenuController from a long press gesture applied to your UILabel. There is an excellent article about this on NSHipster, but the gist of the article is the following.

Create a subclass of UILabel and implement the following methods:

override func canBecomeFirstResponder() -> Bool {
    return true
}

override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
    return (action == "copy:")
}

// MARK: - UIResponderStandardEditActions

override func copy(sender: AnyObject?) {
    UIPasteboard.generalPasteboard().string = text
}

Then in your view controller, you can add a long press gesture to your label:

let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPressGesture:")
label.addGestureRecognizer(gestureRecognizer)

and handle the long press with this method:

func handleLongPressGesture(recognizer: UIGestureRecognizer) {
     if let recognizerView = recognizer.view,
         recognizerSuperView = recognizerView.superview
     {
         let menuController = UIMenuController.sharedMenuController()
         menuController.setTargetRect(recognizerView.frame, inView: recognizerSuperView)
         menuController.setMenuVisible(true, animated:true)
         recognizerView.becomeFirstResponder()
     }}

NOTE: This code is taken directly from the NSHipster article, I am just including it here for SO compliance.

Mozart answered 12/10, 2016 at 0:8 Comment(2)
You also need to enable user interactions on the label. Without that the gesture will never be recognized. And it's also important to check the gesture's state in the handleLongPressGesture method. That NSHipster article is doing a terrible job by not doing that properly.Schwarzwald
Hi! I had to move recognizerView.becomeFirstResponder() before menuController.setMenuVisible to have the first long press workingApron
T
13

Self-contained Solution (Swift 5)

You can adapt the solution from @BJHSolutions and NSHipster to make the following self-contained SelectableLabel:

import UIKit

/// Label that allows selection with long-press gesture, e.g. for copy-paste.
class SelectableLabel: UILabel {
    
    override func awakeFromNib() {
        super.awakeFromNib()

        isUserInteractionEnabled = true
        addGestureRecognizer(
            UILongPressGestureRecognizer(
                target: self,
                action: #selector(handleLongPress(_:))
            )
        )
    }

    override var canBecomeFirstResponder: Bool {
        return true
    }
    
    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        return action == #selector(copy(_:))
    }

    // MARK: - UIResponderStandardEditActions
    
    override func copy(_ sender: Any?) {
        UIPasteboard.general.string = text
    }
    
    // MARK: - Long-press Handler
    
    @objc func handleLongPress(_ recognizer: UIGestureRecognizer) {
        if recognizer.state == .began,
            let recognizerView = recognizer.view,
            let recognizerSuperview = recognizerView.superview {
            recognizerView.becomeFirstResponder()
            UIMenuController.shared.setTargetRect(recognizerView.frame, in: recognizerSuperview)
            UIMenuController.shared.setMenuVisible(true, animated:true)
        }
    }
    
}
Toxicant answered 26/8, 2019 at 3:43 Comment(2)
If you want the action to float centered above the text: ``` let textWidth = sizeThatFits(CGSize(width: frame.size.width, height: CGFloat(MAXFLOAT))).width let adjusted = CGRect(x: 0.0, y: recognizerView.frame.midY, width: textWidth, height: recognizerView.frame.height) UIMenuController.shared.setTargetRect(adjusted, in: recognizerSuperview) ```Adiaphorous
Hi! I had to move recognizerView.becomeFirstResponder() before menuController.setMenuVisible to have the first long press workingApron
M
7

Yes, you need to implement a UIMenuController from a long press gesture applied to your UILabel. There is an excellent article about this on NSHipster, but the gist of the article is the following.

Create a subclass of UILabel and implement the following methods:

override func canBecomeFirstResponder() -> Bool {
    return true
}

override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
    return (action == "copy:")
}

// MARK: - UIResponderStandardEditActions

override func copy(sender: AnyObject?) {
    UIPasteboard.generalPasteboard().string = text
}

Then in your view controller, you can add a long press gesture to your label:

let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPressGesture:")
label.addGestureRecognizer(gestureRecognizer)

and handle the long press with this method:

func handleLongPressGesture(recognizer: UIGestureRecognizer) {
     if let recognizerView = recognizer.view,
         recognizerSuperView = recognizerView.superview
     {
         let menuController = UIMenuController.sharedMenuController()
         menuController.setTargetRect(recognizerView.frame, inView: recognizerSuperView)
         menuController.setMenuVisible(true, animated:true)
         recognizerView.becomeFirstResponder()
     }}

NOTE: This code is taken directly from the NSHipster article, I am just including it here for SO compliance.

Mozart answered 12/10, 2016 at 0:8 Comment(2)
You also need to enable user interactions on the label. Without that the gesture will never be recognized. And it's also important to check the gesture's state in the handleLongPressGesture method. That NSHipster article is doing a terrible job by not doing that properly.Schwarzwald
Hi! I had to move recognizerView.becomeFirstResponder() before menuController.setMenuVisible to have the first long press workingApron
T
0

UILabel inherits from UIView so you can just add a long press gesture recognizer to the label. Note that you have to change isUserInteractionEnabled to true, because it defaults to false for labels.

    import UIKit

    class ViewController: UIViewController {
        let label = UILabel()

        override func viewDidLoad() {
            view.addSubview(label)
            label.text = "hello"
            label.translatesAutoresizingMaskIntoConstraints = false
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
            let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressLabel(longPressGestureRecognizer:)))
            label.addGestureRecognizer(longPressGestureRecognizer)
            label.isUserInteractionEnabled = true
        }

        @objc private func longPressLabel (longPressGestureRecognizer: UILongPressGestureRecognizer) {
            if longPressGestureRecognizer.state == .began {
                print("long press began")
            } else if longPressGestureRecognizer.state == .ended {
                print("long press ended")
            }

        }

    }
Terceira answered 12/10, 2016 at 0:16 Comment(0)
R
-1

I've implemented a UILabel subclass that provides all of the functionality needed. Note that if you're using this with interface builder, you'll need to adjust the init methods.

/// A label that can be copied.
class CopyableLabel: UILabel
{
    // MARK: - Initialisation

    /// Creates a new label.
    init()
    {
        super.init(frame: .zero)

        let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
        self.addGestureRecognizer(gestureRecognizer)

        self.isUserInteractionEnabled = true
    }

    required init?(coder aDecoder: NSCoder)
    {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - Responder chain

    override var canBecomeFirstResponder: Bool
    {
        return true
    }

    // MARK: - Actions

    /// Method called when a long press is triggered.
    func handleLongPressGesture(_ gestuerRecognizer: UILongPressGestureRecognizer)
    {
        guard let superview = self.superview else { return }

        let menuController = UIMenuController.shared
        menuController.setTargetRect(self.frame, in: superview)
        menuController.setMenuVisible(true, animated:true)
        self.becomeFirstResponder()
    }

    override func copy(_ sender: Any?)
    {
        UIPasteboard.general.string = self.text
    }
}
Rounds answered 17/8, 2017 at 9:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.