Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Detect a tap on a URL inside a UITextView. Note: the rs_links method isn't included -- you'll need something that takes text and returns an array of detected links. This gist just demonstrates walking through the UITextView characters.
@implementation UITextView (RSExtras)
static BOOL stringCharacterIsAllowedAsPartOfLink(NSString *s) {
/*[s length] is assumed to be 0 or 1. s may be nil.
Totally not a strict check.*/
if (s == nil || [s length] < 1)
return NO;
unichar ch = [s characterAtIndex:0];
if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:ch])
return NO;
return YES;
}
- (NSString *)rs_potentialLinkAtPoint:(CGPoint)point {
/*Grow a string around the tap until hitting a space, cr, lf, or beginning or end of document.*/
/*If we don't check for end of document, then you could tap way below end of text, and it would return a link if the last text was a link. This has the unfortunate side effect that you can't tap on the last character of a link if it appears at the end of a document. I can live with shipping that.*/
UITextRange *textRange = [self characterRangeAtPoint:point];
UITextPosition *endOfDocumentTextPosition = self.endOfDocument;
if ([textRange.end isEqual:endOfDocumentTextPosition])
return nil;
UITextPosition *tapPosition = [self closestPositionToPoint:point];
if (tapPosition == nil)
return nil;
NSMutableString *s = [NSMutableString stringWithString:@""];
/*Move right*/
UITextPosition *textPosition = tapPosition;
BOOL isFirstCharacter = YES;
while (true) {
UITextRange *rangeOfCharacter = [self.tokenizer rangeEnclosingPosition:textPosition withGranularity:UITextGranularityCharacter inDirection:UITextWritingDirectionNatural];
NSString *oneCharacter = [self textInRange:rangeOfCharacter];
if (isFirstCharacter) {
/*If first character is cr or lf, then we're off the right hand side of the link. Maybe way outside.*/
if ([oneCharacter isEqualToString:@"\n"] || [oneCharacter isEqualToString:@"\r"])
return nil;
}
isFirstCharacter = NO;
if (!stringCharacterIsAllowedAsPartOfLink(oneCharacter))
break;
[s appendString:oneCharacter];
textPosition = [self positionFromPosition:textPosition offset:1];
if (textPosition == nil)
break;
}
/*Move left*/
textPosition = [self positionFromPosition:tapPosition offset:-1];
if (textPosition != nil) {
while (true) {
UITextRange *rangeOfCharacter = [self.tokenizer rangeEnclosingPosition:textPosition withGranularity:UITextGranularityCharacter inDirection:UITextWritingDirectionNatural];
NSString *oneCharacter = [self textInRange:rangeOfCharacter];
if (!stringCharacterIsAllowedAsPartOfLink(oneCharacter))
break;
[s insertString:oneCharacter atIndex:0];
textPosition = [self positionFromPosition:textPosition offset:-1];
if (textPosition == nil)
break;
}
}
return s;
}
- (NSString *)rs_linkAtPoint:(CGPoint)point {
NSString *potentialLink = [self rs_potentialLinkAtPoint:point];
if (potentialLink == nil || [potentialLink length] < 1)
return nil;
NSArray *links = [potentialLink rs_links];
if (links == nil || [links count] < 1)
return nil;
NSString *firstLink = links[0];
return firstLink;
}
@end
@mluisbrown
Copy link

mluisbrown commented Jun 20, 2013

As @shadowofged mentioned above, if you can pre-parse your text to get an NSRange for all the links (regardless if you use NSDataDetector or not) then it's straightforward to hit test that range in the UITextView:

NSRange range = linkRange;

UITextPosition *beginning = textView.beginningOfDocument;
UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
UITextPosition *end = [textView positionFromPosition:start offset:range.length];
UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
CGRect rect = [textView firstRectForRange:textRange];

if (CGRectContainsPoint(rect, point)) {
    // link was tapped - do something   
}

I'm using this in one of my apps and it works very well.

@sudhanshu10
Copy link

sudhanshu10 commented Aug 28, 2013

rs_links implementation?

@scottymac
Copy link

scottymac commented Sep 12, 2013

Unfortunately at the moment I can't characterRangeAtPoint to work in iOS 7 unless the UITextView has or previously had a selection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment