Data Detection for Links

If you need to determine if a text string contains a valid URL link resist the temptation to start constructing a regular expression and take a look at the NSDataDetector class from the Foundation framework.

NSDataDetector

The NSDataDetector Class Reference was introduced to the Foundation framework with iOS 4.0 so has been around for a while. It is defined in NSRegularExpression.h which also has this useful explanation:

NSDataDetector is a specialized subclass of NSRegularExpression. Instead of finding matches to regular expression patterns, it matches items identified by Data Detectors, such as dates, addresses, and URLs.

There is a single class method to initialise a new data detector named +dataDetectorWithTypes:error: where you must specify one or more data detector types to check. As time of writing (iOS 7.1) the possible options are:

  • NSTextCheckingTypeDate
  • NSTextCheckingTypeAddress
  • NSTextCheckingTypeLink
  • NSTextCheckingTypePhoneNumber
  • NSTextCheckingTypeTransitInformation

You can OR these values if you want to check for more than one type. However in this case if I just want to check for links I would initialise a data detector as follows:

NSError *error = nil;
NSDataDetector *detector = [NSDataDetector
  dataDetectorWithTypes:NSTextCheckingTypeLink 
                  error:&error];

Once the data detector is created there are several useful NSRegularExpression methods to check an NSString depending on the desired result:

Counting matches

To simply determine if an NSString contains one or more links use numberOfMatchesInString:options:range:

NSString *stringValue = @"A link: http://useyourloaf.com";
NSUInteger matchCount = [detector numberOfMatchesInString:stringValue
  options:0 range:NSMakeRange(0, stringValue.length)];

Finding first match

To find the first link in the string and extract the URL value use firstMatchInString:options:range:. This method returns an NSTextCheckingResult object which if successful will contain an NSURL object for the detected link:

NSString *stringValue =  @"Two links: useyourloaf.com and apple.com"; 
NSURL *url = nil;
NSTextCheckingResult *result = [detector 
  firstMatchInString:stringValue options:0 
  range:NSMakeRange(0, stringValue.length)];
if (result.resultType == NSTextCheckingTypeLink) {
    url = result.URL;
}
NSLog(@"matched: %@", url);
// matched: http://useyourloaf.com

Note that the resultType value returned in the NSTextCheckingResult indicates the matching data detector type. In this case the data detector is only matching links so it should always contain NSTextCheckingTypeLink and will have an NSURL object. If you are checking for multiple types you should check which one actually matched as the additional information will vary (for example for NSTextCheckingTypePhoneNumber use the phoneNumber property).

Finding all matches

If you want to find all links in the string you can use matchesInString:options:range: and get back an NSArray of NSTextCheckingResult objects (one for each link matched). Alternatively you can use a block method to enumerate all matches:

NSString *stringValue = @"Two links: useyourloaf.com and apple.com";    
[detector enumerateMatchesInString:stringValue options:0
   range:NSMakeRange(0, stringValue.length) usingBlock:
   ^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  if (result.resultType == NSTextCheckingTypeLink) {
    NSLog(@"matched: %@",result.URL);
  }         
}];
// matched: http://useyourloaf.com
// matched: http://apple.com