Detect Touches on Attributed Text in UILabel

I finally found a reliable way to detect touches on specific parts of a UILabel that has NSAttributedString content on its .attributedText property.

(UPDATE: Sadly, I discovered that this technique only works on one line labels, or multi-line labels with their .textAlignment property set to NSTextAlignmentLeft.)

In short, remember that you can assign any key-value pair as an attribute on a NSAttributedString.  So, by assigning custom attributes, you can have multiple ‘callbacks’ associated with taps on that content type.  Imagine a label that has ‘terms of service’ and ‘privacy policy’.  You can give them those specific attributes.

Anyway, here’s a recipe that should get you started.  It’s quick and dirty, and the snippet below would be for example in a UIViewController subclass:

static NSString * const MyCustomAttribute = @"CustomAttribute";  // NSNumber value (BOOL)

- (void)configureAttributedTextLabel
    NSString *linkText = @"Stephen";
    self.attributedTextLabel.text = [NSString stringWithFormat: @"Here we have some super long string that will span multiple lines, just to check.  Isn't that right %@\?", linkText];
    NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:self.attributedTextLabel.text];
    NSRange userNameRange = [self.attributedTextLabel.text rangeOfString:linkText];
    NSRange notUserNameRange = (NSRange){0, userNameRange.location};
    [result setAttributes:@{NSForegroundColorAttributeName: self.attributedTextLabel.textColor,
                            NSFontAttributeName: self.attributedTextLabel.font}
    [result setAttributes:@{NSForegroundColorAttributeName: [QLAppearance primaryColor],
                            NSFontAttributeName: self.attributedTextLabel.font,
                            NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
                            MyCustomAttribute : @YES}
    self.attributedTextLabel.attributedText = result;
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tappedLabel:)];
    self.attributedTextLabel.userInteractionEnabled = YES;
    [self.attributedTextLabel addGestureRecognizer:tap];

- (void)tappedLabel:(UITapGestureRecognizer*)tap
    UILabel *label = (UILabel*)tap.view;
    if (!label.attributedText) {
    NSTextStorage *storage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:label.bounds.size];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [layoutManager addTextContainer:textContainer];
    [storage addLayoutManager:layoutManager];
    textContainer.lineFragmentPadding = 0.0;
    textContainer.lineBreakMode = label.lineBreakMode;
    textContainer.maximumNumberOfLines = label.numberOfLines;
    CGPoint location = [tap locationInView:label];
    NSUInteger characterIndex = [layoutManager characterIndexForPoint:location inTextContainer:textContainer fractionOfDistanceBetweenInsertionPoints:NULL];
    if (characterIndex < storage.length)
        NSLog(@"Character Index: %i", (int)characterIndex);
        NSRange range = NSMakeRange(characterIndex, 1);
        NSString *substring = [label.attributedText.string substringWithRange:range];
        NSLog(@"Character at Index: %@", substring);
        NSString *attributeName = MyCustomAttribute;
        NSNumber *attributeValue = [label.attributedText attribute:attributeName atIndex:characterIndex effectiveRange:nil];
        if (attributeValue) {
            NSLog(@"You tapped on %@ and the value is: %@", attributeName, NSStringFromBOOL(attributeValue.boolValue));
            // use this for further callbacks!

And that’s pretty much it!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s