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}
                    range:notUserNameRange];
    
    [result setAttributes:@{NSForegroundColorAttributeName: [QLAppearance primaryColor],
                            NSFontAttributeName: self.attributedTextLabel.font,
                            NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
                            MyCustomAttribute : @YES}
                    range:userNameRange];
    
    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) {
        return;
    }
    
    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!

Advertisements

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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