Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create tap-able "links" in the NSAttributedString of a UILabel?

I have been searching this for hours but I've failed. I probably don't even know what I should be looking for.

Many applications have text and in this text are web hyperlinks in rounded rect. When I click them UIWebView opens. What puzzles me is that they often have custom links, for example if words starts with # it is also clickable and the application responds by opening another view. How can I do that? Is it possible with UILabel or do I need UITextView or something else?

like image 812
Lope Avatar asked Aug 10 '09 19:08

Lope


People also ask

How can I make a clickable link in an Nsattributedstring?

You just set the "detect links" checkbox on the view in IB, and it detects HTTP links and turns them into hyperlinks. However, that still means that what the user sees is the "raw" link. RTF files and HTML both allow you to set up a user-readable string with a link "behind" it.

How do you make a UILabel clickable in Swift?

If needed, it is possible to make UILabel clickable in Swift although it is recommended to use UIButton instead. To make UILabel clickable in Swift, you will need to create a UITapGestureRecognizer and then add it to a label.


1 Answers

In general, if we want to have a clickable link in text displayed by UILabel, we would need to resolve two independent tasks:

  1. Changing the appearance of a portion of the text to look like a link
  2. Detecting and handling touches on the link (opening an URL is a particular case)

The first one is easy. Starting from iOS 6 UILabel supports display of attributed strings. All you need to do is to create and configure an instance of NSMutableAttributedString:

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"String with a link" attributes:nil]; NSRange linkRange = NSMakeRange(14, 4); // for the word "link" in the string above  NSDictionary *linkAttributes = @{ NSForegroundColorAttributeName : [UIColor colorWithRed:0.05 green:0.4 blue:0.65 alpha:1.0],                                   NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle) }; [attributedString setAttributes:linkAttributes range:linkRange];  // Assign attributedText to UILabel label.attributedText = attributedString; 

That's it! The code above makes UILabel to display String with a link

Now we should detect touches on this link. The idea is to catch all taps within UILabel and figure out whether the location of the tap was close enough to the link. To catch touches we can add tap gesture recognizer to the label. Make sure to enable userInteraction for the label, it's turned off by default:

label.userInteractionEnabled = YES; [label addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapOnLabel:)]];  

Now the most sophisticated stuff: finding out whether the tap was on where the link is displayed and not on any other portion of the label. If we had single-lined UILabel, this task could be solved relatively easy by hardcoding the area bounds where the link is displayed, but let's solve this problem more elegantly and for general case - multiline UILabel without preliminary knowledge about the link layout.

One of the approaches is to use capabilities of Text Kit API introduced in iOS 7:

// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero]; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];  // Configure layoutManager and textStorage [layoutManager addTextContainer:textContainer]; [textStorage addLayoutManager:layoutManager];  // Configure textContainer textContainer.lineFragmentPadding = 0.0; textContainer.lineBreakMode = label.lineBreakMode; textContainer.maximumNumberOfLines = label.numberOfLines; 

Save created and configured instances of NSLayoutManager, NSTextContainer and NSTextStorage in properties in your class (most likely UIViewController's descendant) - we'll need them in other methods.

Now, each time the label changes its frame, update textContainer's size:

- (void)viewDidLayoutSubviews {     [super viewDidLayoutSubviews];     self.textContainer.size = self.label.bounds.size; } 

And finally, detect whether the tap was exactly on the link:

- (void)handleTapOnLabel:(UITapGestureRecognizer *)tapGesture {     CGPoint locationOfTouchInLabel = [tapGesture locationInView:tapGesture.view];     CGSize labelSize = tapGesture.view.bounds.size;     CGRect textBoundingBox = [self.layoutManager usedRectForTextContainer:self.textContainer];     CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,                                               (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);     CGPoint locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,                                                          locationOfTouchInLabel.y - textContainerOffset.y);     NSInteger indexOfCharacter = [self.layoutManager characterIndexForPoint:locationOfTouchInTextContainer                                                             inTextContainer:self.textContainer                                    fractionOfDistanceBetweenInsertionPoints:nil];     NSRange linkRange = NSMakeRange(14, 4); // it's better to save the range somewhere when it was originally used for marking link in attributed string     if (NSLocationInRange(indexOfCharacter, linkRange)) {         // Open an URL, or handle the tap on the link in any other way         [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://stackoverflow.com/"]];     } } 
like image 132
nalexn Avatar answered Oct 04 '22 11:10

nalexn