tags:

views:

65

answers:

1

Hello!

I have UILabel and I need it to be able support copy&paste (actually only copy as it is read only). I subclassed UILabel to support copy and it works fine. I also added text highlight so user knows what exactly is he copying when he clicks on label.

My problem is that I don't know how to cancel that highlight when user clicks somewhere else. Copy bubble disappears, but I don't get any callback so text remains highlighted. Is there special callback I missed that I can use or do I have to come up with some dirty hack? Or is there perhaps more standard way of highlighting text in UILabel that I am not aware of that would be handled automatically like Copy bubble?

Here my custom UILabel code:

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

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

        UIMenuController *menu = [UIMenuController sharedMenuController];
        [menu setTargetRect:self.bounds inView:self];
        [menu setMenuVisible:YES animated:YES];

        return YES;
    }
    return NO;
}

- (BOOL)resignFirstResponder {
    if([super resignFirstResponder]) {
        self.highlighted = NO;

        UIMenuController *menu = [UIMenuController sharedMenuController];
        [menu setMenuVisible:NO animated:YES];
        [menu update];

        return true;
    }
    return false;
}

- (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]) {
        //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];
    }
}

- (void)setHighlighted:(BOOL)hl {
    [super setHighlighted:hl];

    [self setNeedsLayout];
}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];

    if(self.highlighted) {
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextSetRGBFillColor(ctx, 0.3, 0.8, 1.0, 0.3);
        CGContextAddRect(ctx, CGRectMake(0, 0, [self textRectForBounds:self.frame limitedToNumberOfLines:1].size.width, self.frame.size.height));
        CGContextFillPath(ctx);
    }
}

Any help appreciated!

+1  A: 

Personally i would use a UITextView with option of editable set to NO.

otherwise, if you want more control, subclass your topmost fullscreen view (or window) that your UILabel is a child of.

override - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

and if the hit view is not your UILabel, then create a NSNotification which you handle in your UILabel subclass, and do the deselect.

BTW, You should ALWAYS handle touchesCanceled also!

Nick H247
Thanks for tips. I think there was reason why I didn't use UITextView, but I don't remember what it was. I will check it. I didn't know about hittest method, I will try that one too. About touchesCancel, I suppose it is ok to just do same stuff as in touchesEnded in most cases, right?
Lope
would be interested to know why you didn't use a UITextView...
Nick H247
yep i think just a copy of the touchesEnded should be fine.
Nick H247
ok, overriding hitTest helped. Everything works now as expected. Huge thanks! I learned a lot with this :) Btw I probably didn't use UITextView because it is less customizable in interface builder than UILabel (font, background color...)
Lope
though, you can set all of that programatically quite easily! But, glad to be of service.
Nick H247