For the ProductLayer sign-up form I wanted to make sure that the user can only send a sign up if the email address is valid. If you google for ways to validate an email address you most often solutions involving regular expressions. But since I don’t trust a RegEx unless I know it by heart, I implemented the validation with Apple’s own NSDataDetector for links.
The most highly voted result on a related question on Stack Overflow looks like this:
- (BOOL)validateEmail:(NSString *)candidate { NSString *emailRegex = @"(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}" @"~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\" @"x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-" @"z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5" @"]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-" @"9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21" @"-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"; NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES[c] %@", emailRegex]; return [emailTest evaluateWithObject:candidate]; } |
The code might be readable, but such a regular expression makes shivers run up and down my spine.
NSDataDetector is a subclass of NSRegularExpression which internally knows the patterns for a great variety of things it can detect: Phone numbers, addresses, dates, transit information and many more. This is also the way how Apple detects mail addresses and links in Safari or Mail.app.
You can create a data detector for type NSTextCheckingTypeLink and it will include both web addresses as well as email addresses. All that remains are some cursory checks to further narrow down the validation.
- (BOOL)_isValidEmail:(NSString *)email { if (![email length]) { return NO; } NSRange entireRange = NSMakeRange(0, [email length]); NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:NULL]; NSArray *matches = [detector matchesInString:email options:0 range:entireRange]; // should only a single match if ([matches count]!=1) { return NO; } NSTextCheckingResult *result = [matches firstObject]; // result should be a link if (result.resultType != NSTextCheckingTypeLink) { return NO; } // result should be a recognized mail address if (![result.URL.scheme isEqualToString:@"mailto"]) { return NO; } // match must be entire string if (!NSEqualRanges(result.range, entireRange)) { return NO; } // but schould not have the mail URL scheme if ([email hasPrefix:@"mailto:"]) { return NO; } // no complaints, string is valid email address return YES; } |
The data detector infers the mailto: URL scheme and possible multiple link matches in the passed string. The ifs make sure that “mailto:something” is not recognized as a valid email address.
Granted the above is more code to copy/paste but I have a better feeling using the on-board method for detecting a valid email address. Here I have some hope that Apple will make sure the underlying regular expressions are solid.
Categories: Recipes
I have doubts about `if (![matches count]==1)` part
`![matches count]` (if none found) would convert 0 (NO) to 1 (YES) which would turn condition to true, so if one match is found then you get a false negative.
May be you meant `if ([matches count] !=1 )` which would match 0 or more than 1 match being found?
Eimantas Vaičiūnas, thanks for spotting my mistake.
This code is not spotting the error in case of ab..c@example.com . Please help!