Implementing iphone's copy/paste controls on a custom view / uiview subclass

Billy Gray picture Billy Gray · Jan 21, 2010 · Viewed 8.4k times · Source

I will admit that there is already a question exactly along these lines here on S.O., but it lacks implementation details, a working answer, and I would like to be more specific, so I think a new question is in order. Obviously, let me know if I'm wrong and we can try to restart the thread over there.

Basically, I want to copy the text in a UILabel to the pasteboard when a user holds down on the label. Not hard to do, honestly. However, I think the best way to provide visual feedback is to prompt the user with the Copy menu option from UIMenuController.

According to the Event Handling section of the iPhone Application Programming Guide, specifically the section on Copy, Cut, and Paste Operations, it should be possible to provide copy, cut, and/or paste operations from a custom view.

So, I've sub-classed UILabel with the following implementation as described by the guide, but the UIMenuController won't show up. There's no indication in the guide that there's anything else required to do this, and the NSLog statement does print out to the console, indicating that the selector is being performed when I hold down on the label:

//
//  CopyLabel.m
//  HoldEm
//
//  Created by Billy Gray on 1/20/10.
//  Copyright 2010 Zetetic LLC. All rights reserved.
//

#import "CopyLabel.h"

@implementation CopyLabel

- (void)showCopyMenu {
    NSLog(@"I'm tryin' Ringo, I'm tryin' reeeeal hard.");
    // bring up editing menu.
    UIMenuController *theMenu = [UIMenuController sharedMenuController];
    // do i even need to show a selection? There's really no point for my implementation...
    // doing it any way to see if it helps the "not showing up" problem...
    CGRect selectionRect = [self frame];
    [theMenu setTargetRect:selectionRect inView:self];
    [theMenu setMenuVisible:YES animated:YES]; // <-- doesn't show up...
}

// obviously, important to provide this, but whether it's here or not doesn't seem
// to change the fact that the UIMenuController view is not showing up
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    BOOL answer = NO;

    if (action == @selector(copy:))
        answer = YES;

    return answer;
}

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self performSelector:@selector(showCopyMenu) withObject:nil afterDelay:0.8f];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showCopyMenu) object:nil];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showCopyMenu) object:nil];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showCopyMenu) object:nil];
}

@end

So, what else does one have to do to make this happen?

For those following along and trying to do this, too, you'll also need to set 'User Interaction Enabled' for the label

Edit:

For clarity, let me add that this should be similar to the little [Copy] menu item that appears over an image in certain iphone views when you hold down on it. -B

Answer

slf picture slf · Jan 21, 2010

I'll say upfront I don't have an aswer, but I did some poking around and found more out. I'm sure you've looked at this already: CopyPasteTile

That code does work on my simulator and goes like this:

CGRect drawRect = [self rectFromOrigin:currentSelection inset:TILE_INSET];
[self setNeedsDisplayInRect:drawRect];

UIMenuController *theMenu = [UIMenuController sharedMenuController];
[theMenu setTargetRect:drawRect inView:self];
[theMenu setMenuVisible:YES animated:YES];

There are a few differences here:

  • drawRect is calculated from a giant view tile and tap point calculations
  • setNeedsDisplayInRect is being called
  • self is a large screen sized view, you may need screen coords instead of local coords (you can probably get that from self.superview)

I'd try making these adjustments to match the example first and see what kind of progress it gets me.