Skip to content

Instantly share code, notes, and snippets.

@brentsimmons
Last active January 3, 2021 02:22
Show Gist options
  • Save brentsimmons/5810992 to your computer and use it in GitHub Desktop.
Save brentsimmons/5810992 to your computer and use it in GitHub Desktop.
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
@scottymac
Copy link

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