Reputation: 2001
Swift 5, Xcode 10, iOS 12
My code uses UIApplication.shared.canOpenURL
to validate URLs, which unfortunately fails without e.g. "http://".
Example:
print(UIApplication.shared.canOpenURL(URL(string: "stackoverflow.com")!)) //false
print(UIApplication.shared.canOpenURL(URL(string: "http://stackoverflow.com")!)) //true
print(UIApplication.shared.canOpenURL(URL(string: "129.0.0.1")!)) //false
print(UIApplication.shared.canOpenURL(URL(string: "ftp://129.0.0.1")!)) //true
I'm aware of the change with schemes (iOS9+) and I know that I can just add a prefix like "http://" if the String doesn't start with it already, then check this new String but I'm still wondering:
Question: How do I add a "there's no scheme" scheme, so valid URLs like "stackoverflow.com" return true
too (is this even possible?)?
Upvotes: 3
Views: 5011
Reputation: 13354
It's not possible to add a valid scheme to URL
because no one knows which prefix will be add to which URL
. You can just validate a URL
with the help of regex
.
I searched and modified the regex.
extension String {
func isValidUrl() -> Bool {
let regex = "((http|https|ftp)://)?((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+"
let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
return predicate.evaluate(with: self)
}
}
I tested it with below urls:
print("http://stackoverflow.com".isValidUrl())
print("stackoverflow.com".isValidUrl())
print("ftp://127.0.0.1".isValidUrl())
print("www.google.com".isValidUrl())
print("127.0.0.1".isValidUrl())
print("127".isValidUrl())
print("hello".isValidUrl())
Output
true
true
true
true
true
false
false
Note: 100% regex is not possible to validate the email
and url
Upvotes: 12
Reputation: 1979
This is the method that I use
extension String {
/// Return first available URL in the string else nil
func checkForURL() -> NSRange? {
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return nil
}
let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count))
for match in matches {
guard Range(match.range, in: self) != nil else { continue }
return match.range
}
return nil
}
func getURLIfPresent() -> String? {
guard let range = self.checkForURL() else{
return nil
}
guard let stringRange = Range(range,in:self) else {
return nil
}
return String(self[stringRange])
}
}
Apparently, the method name and the comment in the code are not verbose enough, so here is the explanation.
Used NSDataDetector and provided it the type - NSTextCheckingResult.CheckingType.link to check for links.
This goes through the string provided and returns all the matches for URL type.
This checks for link in the string that you provide, if any, else returns nil.
The method getURLIfPresent return the URL part from that string.
Here are a few examples
print("http://stackoverflow.com".getURLIfPresent())
print("stackoverflow.com".getURLIfPresent())
print("ftp://127.0.0.1".getURLIfPresent())
print("www.google.com".getURLIfPresent())
print("127.0.0.1".getURLIfPresent())
print("127".getURLIfPresent())
print("hello".getURLIfPresent())
Output
Optional("http://stackoverflow.com")
Optional("stackoverflow.com")
Optional("ftp://127.0.0.1")
Optional("www.google.com")
nil
nil
nil
But, this doesn't return true for "127.0.0.1". So I don't think it will fulfil your cause. In your case, going the regex way is better it seems. As you can add more conditions if you come across some more patterns that demand to be considered as URL.
Upvotes: 1