-
Notifications
You must be signed in to change notification settings - Fork 18
Home
Unlike UITextView
, UILabel
s 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 yourUILabel
is set toYES
- Add an
UITapGestureRecognizer
on yourUILabel
, and associate it with aselector
/IBAction (let's call ithandleLabelTap:
) - 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 anUITapGestureRecognized
to each of them with the samehandleLabelTap:
action/callback for all of them. Thelabel: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.