21

I want the text in a UITextField (or ideally, a UILabel) to be non-editable, but at the same time give the user the ability to copy it to paste elsewhere.

Elijah
  • 8,381
  • 2
  • 55
  • 49
mrueg
  • 8,185
  • 4
  • 44
  • 66

7 Answers7

25

My final solution was the following:

I created a subclass of UILabel (UITextField should work the same) that displays a UIMenuController after being tapped. CopyableLabel.m looks like this:

@implementation CopyableLabel


- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if(action == @selector(copy:)) {
    return YES;
}
else {
    return [super canPerformAction:action withSender:sender];
}
}


- (BOOL)canBecomeFirstResponder {
return YES;
}


- (BOOL)becomeFirstResponder {
if([super becomeFirstResponder]) {
    self.highlighted = YES;
    return YES;
}
return NO;
}


- (void)copy:(id)sender {
UIPasteboard *board = [UIPasteboard generalPasteboard];
[board setString:self.text];
self.highlighted = NO;
[self resignFirstResponder];
}


- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if([self isFirstResponder]) {
    self.highlighted = NO;
    UIMenuController *menu = [UIMenuController sharedMenuController];
    [menu setMenuVisible:NO animated:YES];
    [menu update];
    [self resignFirstResponder];
}
else if([self becomeFirstResponder]) {
    UIMenuController *menu = [UIMenuController sharedMenuController];
    [menu setTargetRect:self.bounds inView:self];
    [menu setMenuVisible:YES animated:YES];
}
}


@end
mrueg
  • 8,185
  • 4
  • 44
  • 66
  • this method doesnt seem to work when used inside a tableViewController, but you can make it work if you add this in your view did load `UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc] initWithTarget:self.yourCopyableLabel action:@selector(tapDetected)]; [self.view addGestureRecognizer:tgr];` then change the `- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event` in the copyableLabel implementation to `- (void) tapDetected` and it should work – Fonix May 08 '14 at 14:43
  • also add the `UITapGestureRecogniser` to a particular cell if you can, the way i mentioned above makes the menu popup if you click anywhere on the screen that is not a clickable item – Fonix May 08 '14 at 15:28
6

This question is pretty old and I'm surprised nobody has posted a solution without subclassing. The idea presented in @mrueg's answer is correct, but you shouldn't need to subclass anything. I just came across this problem and solved it like this:

In my view controller:

- (void)viewDidLoad {
    self.textField.delegate = self;
    self.textField.text = @"Copyable, non-editable string.";
}

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (void)copyTextFieldContent:(id)sender {
    UIPasteboard* pb = [UIPasteboard generalPasteboard];
    pb.string = self.textField.text;
}

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
    // UIKit changes the first responder after this method, so we need to show the copy menu after this method returns.
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         [self becomeFirstResponder];
         UIMenuController* menuController = [UIMenuController sharedMenuController];
         UIMenuItem* copyItem = [[UIMenuItem alloc] initWithTitle:@"Copy"
                                                           action:@selector(copyTextFieldContent:)];
         menuController.menuItems = @[copyItem];
         CGRect selectionRect = textField.frame;
         [menuController setTargetRect:selectionRect inView:self.view];
         [menuController setMenuVisible:YES animated:YES];
     });
     return NO;
}

If you want to make this work for a UILabel, it should work the same way with just adding a tap gesture recognizer instead of using the delegate method.

Or Arbel
  • 2,965
  • 2
  • 30
  • 40
Shinigami
  • 2,123
  • 23
  • 40
5

This will do everything you need. Will be copyable. But not editable, and won't show a keyboard or a cursor.

class ViewController: UIViewController {

    @IBOutlet weak var copyableUneditableTextfield: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        copyableUneditableTextfield.delegate = self
        copyableUneditableTextfield.inputView = UIView()   //prevents keyboard     
        copyableUneditableTextfield.tintColor = .clear     //prevents cursor
        copyableUneditableTextfield.text = "Some Text You Want User To Copy But Not Edit"

    }

}

extension ViewController: UITextFieldDelegate {

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        return false //prevents editing
    }

}
Elijah
  • 8,381
  • 2
  • 55
  • 49
  • I can still edit the text field even after trying your code – pale bone Feb 22 '18 at 03:02
  • @palebone sorry, I forgot to indicate that you need to make ViewController the delegate for copyableUneditableTextfield. I have updated the code, adding that line. – Elijah Feb 22 '18 at 17:11
  • I think I tried that, it's much easier to just use a uitexview and make it interactable but uneditable – pale bone Feb 22 '18 at 17:13
  • This seems to work fine if you just want the text to be copyable and don't care if it gets changed because an external keyboard can still type in the field. This could be fixed by implementing a couple more delegate methods or it this behavior is acceptable then the delegate can be set to **nil** and the user will get select/copy as wanted but will also get paste and external keyboard input. – LavaSlider Feb 07 '20 at 11:49
3

Try UITextView instead (I suspect it would work like a UILabel for you). I tested this with its editable property set to NO, and double-tapping-to-copy worked for me.

Alex Reynolds
  • 95,983
  • 54
  • 240
  • 345
  • I would suspect though that with the field uneditable, no paste button would appear. Let us know if that's true. – wkw Dec 17 '09 at 14:36
  • 3
    The original question is not written clearly, then. I read "ability to copy and paste" to mean pasting it somewhere else. Reading it the other way, how can you possibly paste something into a UI widget that is *not* editable? I don't think such a widget exists. – Alex Reynolds Dec 17 '09 at 19:49
1

Another solution is keeping the UITextField enabled but programmatically preventing it from being edited. This is done with the following delegate method:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    return NO;
}

I'm not aware of possible limitations though, currently suits my needs.

keeshux
  • 592
  • 3
  • 10
1

How about using UITextView?

yourTextView.isEditable = false

You can copy but can't edit.

0

The following code saved me.

textField.addTarget(target, action: "textFieldEditingDidEndAction:", forControlEvents: [.EditingDidEnd])

It seems Paste is a single and complete edit event.

Veight Zhou
  • 969
  • 1
  • 7
  • 8