Skip to content
AliSoftware edited this page Jan 21, 2015 · 7 revisions

F.A.Q.

How do I detect a link in a UILabel

Unlike UITextView, UILabels don't have a delegate method called when a linked is tapped. It is up to you to detect the tap and the determine if it happened on a character that has an associated link, and act accordingly.

My solution to implement this (iOS7+) uses TextKit's NSLayoutManager to compute which character is under the tapped position. Here is how to do it:

  • Ensure the userInteractionEnabled property of your UILabel is set to YES
  • Add an UITapGestureRecognizer on your UILabel, and associate it with a selector/IBAction (let's call it handleLabelTap:)
  • Implement that callback this way:
- (IBAction)handleLabelTap:(UITapGestureRecognizer *)tapGR
{
    // Only execute the rest of the code if the gesture ended (not during its intermediate states)
    if (tapGR.state != UIGestureRecognizerStateEnded) return;

    // Get the tapped label and the point where the tap occurred in the label
    UILabel* label = (UILabel*)tapGR.recognizedView;
    CGPoint tapPoint = [tapGR locationInView:label];

    // Create the layoutManager & textContainer to compute how characters are positionned
    NSLayoutManager *layoutManager = [NSLayoutManager new];
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
    [textStorage addLayoutManager:layoutManager];
    
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:label.bounds.size];
    textContainer.lineFragmentPadding  = 0;
    textContainer.maximumNumberOfLines = (NSUInteger)label.numberOfLines;
    textContainer.lineBreakMode = label.lineBreakMode;
    [layoutManager addTextContainer:textContainer];
    
    // ask the layoutManager which character is under this tapped point
    NSUInteger charIdx = [layoutManager characterIndexForPoint:tapPoint
                                               inTextContainer:textContainer
                      fractionOfDistanceBetweenInsertionPoints:NULL];

    // the previous method returns the last character if the point is past the last char
    // so we ensure the tap is not after the end of the text
    CGRect textRect = [layoutManager usedRectForTextContainer:textContainer];
    if (CGRectContainsPoint(textRect, tapPoint))
    {
        // Get the underlying URL.
        NSURL* url = [label.attributedText URLAtIndex:charIdx effectiveRange:NULL];
        if (url)
        {
            [self label:label didTapOnURL:url];
        }
    }
}

- (void)label:(UILabel*)label didTapOnURL:(NSURL*)url
{
  // Act accordingly
}

You can now set the attributedText property of your UILabel to an NSAttributedString with a link in it:

NSMutableAttributedString* str = [NSMutableAttributedString attributedStringWithString:@"Hello world!"];
NSURL* url = [NSURL URLWithString:@"https://github.com"];
[str setURL:someURL range:NSMakeRange(6,5)];
self.label.attributedText = str;

Given this implementation, you can even add more than one UILabel to your interface and add an UITapGestureRecognized to each of them with the same handleLabelTap: action/callback for all of them. The label:didTapOnURL: method will be called if an URL has been tapped on any of those labels and you can test which label and URL to act accordingly.

Clone this wiki locally