Categories
Foundation iOS Swift

Property wrapper for validating email using NSDataDetector

Property wrappers allow property declaration to state what kind of property wrapper is used for implementing the property. We can use it for implementing transformations on properties like validating if string is email or not. This is what we will do: creating a property wrapper for email properties and validating emails using NSDataDetector. If value being set is email, we store it and if it is not, we set the property to nil instead.

Creating property wrapper

Property wrappers are types annotated with @propertyWrapper. The type needs to implement one property: wrappedValue. Emails are represented with strings, therefore our wrappedValue property is optional string. Optional is required, because string can contain invalid email and in that case we set the property to nil. Whenever we would like to use this property wrapper, we just need to add @EmailValidated in front of the property definition.

@propertyWrapper
struct EmailValidated {
private var value: String?
var wrappedValue: String? {
get {
return value
}
set {
value = {
guard let trimmedString = newValue?.trimmingCharacters(in: .whitespacesAndNewlines) else { return nil }
return validate(trimmedString)
}()
}
}
}

Validating email using NSDataDetector

Validating emails using regular expressions is not easy. Fortunately Apple provides API exactly for this: NSDataDetector. We can create an instance of NSDataDetector with specifying link as detected types. When matching emails, we use anchored option as we expect the string to only include email, nothing else. Anchored will tell the data detector to match starting with the first character. As firstMatch(in:options:range:) uses NSRange, we need to convert Swift’s range to NSRange because those ranges do not have one-to-one match. For this, we can use special NSRange initialiser taking Swift string and its range.

NSDataDetector represents links with URL, therefore we will see if match contains an URL and if URL’s scheme is mailto. If it is, we can extract the matched email and return it.

private func validate(_ emailString: String) -> String? {
let dataDetector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
// Finding matches in string
let range = NSRange(emailString.startIndex..<emailString.endIndex, in: emailString)
guard let match = dataDetector.firstMatch(in: emailString, options: .anchored, range: range) else { return nil }
guard let url = match.url else { return nil }
// Extracting email from the matched url
let absoluteString = url.absoluteString
guard let index = absoluteString.range(of: "mailto:") else { return nil }
return String(url.absoluteString.suffix(from: index.upperBound))
}
view raw Validate.swift hosted with ❤ by GitHub

Using EmailValidated property wrapper

For using the created property wrapper, all we need to do is to annotate property with @EmailValidated.

struct Contact {
var fullName: String
@EmailValidated var email: String?
}
var contact = Contact(fullName: "Toomas")
contact.email = "invalidemail"
print(contact.email as Any) // nil
contact.email = " test toomas@email.zz"
print(contact.email as Any) // nil
contact.email = "toomas@email.zz"
print(contact.email as Any) // Optional("toomas@email.zz")

Summary

We created a simple property wrapper for validating emails. We saw that creating a property wrapper for validating email with NSDataDetector requires only a little bit of code.

If this was helpful, please let me know on Mastodon@toomasvahter orĀ Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Example

EmailPropertyWrapper playground (Xcode 11b5)

Resources

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s