2

In my app users can send messages to each other. I use UITextView inside of a bubble image to display the chat history.

        [messageTextView setFrame:CGRectMake(padding, padding+5, size.width,     size.height+padding)];
        [messageTextView sizeToFit];
        messageTextView.backgroundColor=[UIColor clearColor];

        UIImage *img = [UIImage imageNamed:@"whiteBubble"];
        UIImageView *bubbleImage=[[UIImageView alloc] initWithImage:[img stretchableImageWithLeftCapWidth:24 topCapHeight:15]];

        messageTextView.editable=NO;

        [bubbleImage setFrame:CGRectMake(padding/2, padding+5,
                                         messageTextView.frame.size.width+padding/2, messageTextView.frame.size.height+5)];


        [cell.contentView addSubview:bubbleImage];
        [cell.contentView addSubview:messageTextView];

Currently, when a user holds down on the message text, they see the 'Copy' and 'Define' options with cursors to select text.

However, I would rather have the basic iOS messaging option of holding down on a chat bubble to copy the entire message. How can this be achieved?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Adam North
  • 49
  • 5

1 Answers1

3

I would subclass UITextView to implement your own version of the copy menu. You can do it a number of ways, but one possible way is like below.

The basic idea is that the text view sets up a UILongPressGestureRecognizer that will create the popup menu when a long press is detected.

UILongPressGestureRecognizer has several default system menus that will show up unless you tell them not to. The way to do that is to return NO for any selectors that you don't want to handle in canPerformAction:withSender:. In this case, we're returning NO for any selector except for our custom copyText: selector.

Then that selector just gets a reference to the general UIPasteboard and sets it's text to the text of the TextView.

In your subclass's implementation:

@implementation CopyTextView

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self setup];
    }
    return self;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self setup];
    }
    return self;
}

- (void)setup {
    self.editable = NO;
    self.selectable = NO;

    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressDetected:)];
    longPress.minimumPressDuration = 0.3f; // however long, in seconds, you want the user to have to press before the menu shows up
    [self addGestureRecognizer:longPress];
}

- (void)longPressDetected:(id)sender {
    [self becomeFirstResponder];

    UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
    if (longPress.state == UIGestureRecognizerStateEnded) {
        UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Copy" action:@selector(copyText:)];

        UIMenuController *menuCont = [UIMenuController sharedMenuController];

        [menuCont setTargetRect:self.frame inView:self.superview];

        menuCont.arrowDirection = UIMenuControllerArrowDown;
        menuCont.menuItems = [NSArray arrayWithObject:menuItem];
        [menuCont setMenuVisible:YES animated:YES];
    }
}


- (BOOL)canBecomeFirstResponder { return YES; }

- (void)copyText:(id)sender {
    UIPasteboard * pasteboard = [UIPasteboard generalPasteboard];
    [pasteboard setString:self.text];
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    if (action == @selector(copyText:)) return YES;
    return NO;
}

@end

Useful documentation:

UILongPressGestureRecognizer Documentation

UIMenuController Documentation

Josh Hudnall
  • 1,021
  • 8
  • 16