Make UILabel Copyable in Swift

I've been working on an iOS app that called for some text to be copyable but not editable. I wanted to able to long-press and have the contextual menu show the "Copy" action.

I'd initially thought about using a UITextField and disabling the edit functionality to achieve this. However that caused some issues with keyboards popping up even when editing was disabled. It struck me as strange that a UILabel isn't copyable. At least by default, as it turns out is actually fairly easy to make it so we can copy and paste out label value.

Subclassing UILabel

To make it copyable we need to subclass UILabel. While we're here we'll chuck in our two init methods and enable user interaction.

import UIKit

class SRCopyableLabel: UILabel {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        sharedInit()
    }
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        sharedInit()
    }
    
    func sharedInit() {
        userInteractionEnabled = true
    }
    
}

In order to to be able to show a UIMenuController instance our label needs to be able to become a first responder. As canBecomeFirstResponder() is inherited from NSObject we can use that to override the default behaviour.

class SRCopyableLabel: UILabel {
    
    ...

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

Copy + Show Menu

Next, we'll implement our copy method that'll take our label's text property and copy it to the system pasteboard.

class SRCopyableLabel: UILabel {
    
    ...

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

If you wanted or needed to you could always copy something else to the pasteboard here. Also not that this is an overridden method. UIView gets access to this from UIResponder.

We'll be showing the standard context menu which comes from UIMenuController.sharedMenuController(). Let's make a showMenu method and trigger it with a UILongPressGestureRecognizer.

class SRCopyableLabel: UILabel {
    
    ...

    func sharedInit() {
        userInteractionEnabled = true
        addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: "showMenu:"))
    }
    
    func showMenu(sender: AnyObject?) {
        becomeFirstResponder()
        let menu = UIMenuController.sharedMenuController()
        if !menu.menuVisible {
            menu.setTargetRect(bounds, inView: self)
            menu.setMenuVisible(true, animated: true)
        }
    }
    
}

Here, we're making our label first responder in order to be able to show our contextual menu. We then check to see if it's already visible and if not show it.

By default, UILabel doesn't allow any actions to be performed. We need to override that to allow for copying.

class SRCopyableLabel: UILabel {
    
    ...

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

Finally, we just need to hide the UIMenu when the copy action is performed.

class SRCopyableLabel: UILabel {
    
    ...

    override func copy(sender: AnyObject?) {
        let board = UIPasteboard.generalPasteboard()
        board.string = text
        let menu = UIMenuController.sharedMenuController()
        menu.setMenuVisible(false, animated: true)
    }
    
}

SRCopyableLabel

And here's our final copyable label. Just add a label as standard in Storyboard and change the class to ours.

✌️