Search code examples
iosobjective-cuitextviewtextkit

Detecting taps on attributed text in a UITextView in iOS


I have a UITextView which displays an NSAttributedString. This string contains words that I'd like to make tappable, such that when they are tapped I get called back so that I can perform an action. I realise that UITextView can detect taps on a URL and call back my delegate, but these aren't URLs.

It seems to me that with iOS 7 and the power of TextKit this should now be possible, however I can't find any examples and I'm not sure where to start.

I understand that it's now possible to create custom attributes in the string (although I haven't done this yet), and perhaps these will be useful to detecting if one of the magic words has been tapped? In any case, I still don't know how to intercept that tap and detect on which word the tap occurred.

Note that iOS 6 compatibility is not required.


Solution

  • I just wanted to help others a little more. Following on from Shmidt's response it's possible to do exactly as I had asked in my original question.

    1) Create an attributed string with custom attributes applied to the clickable words. eg.

    NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:@"a clickable word" attributes:@{ @"myCustomTag" : @(YES) }];
    [paragraph appendAttributedString:attributedString];
    

    2) Create a UITextView to display that string, and add a UITapGestureRecognizer to it. Then handle the tap:

    - (void)textTapped:(UITapGestureRecognizer *)recognizer
    {
        UITextView *textView = (UITextView *)recognizer.view;
    
        // Location of the tap in text-container coordinates
    
        NSLayoutManager *layoutManager = textView.layoutManager;
        CGPoint location = [recognizer locationInView:textView];
        location.x -= textView.textContainerInset.left;
        location.y -= textView.textContainerInset.top;
    
        // Find the character that's been tapped on
    
        NSUInteger characterIndex;
        characterIndex = [layoutManager characterIndexForPoint:location
                                               inTextContainer:textView.textContainer
                      fractionOfDistanceBetweenInsertionPoints:NULL];
    
        if (characterIndex < textView.textStorage.length) {
    
            NSRange range;
            id value = [textView.attributedText attribute:@"myCustomTag" atIndex:characterIndex effectiveRange:&range];
    
            // Handle as required...
    
            NSLog(@"%@, %d, %d", value, range.location, range.length);
    
        }
    }
    

    So easy when you know how!