You can use the NSURL type (whose constructor returns an optional type) combined with an if-let statement to check the validity of a given URL. In other words, make use of the NSURLfailable initializer, a key feature of Swift:
let stringWithPossibleURL: String = self.textField.text // Or another source of text
if let validURL: NSURL = NSURL(string: stringWithPossibleURL) {
// Successfully constructed an NSURL; open it
UIApplication.sharedApplication().openURL(validURL)
} else {
// Initialization failed; alert the user
let controller: UIAlertController = UIAlertController(title: "Invalid URL", message: "Please try again.", preferredStyle: .Alert)
controller.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.presentViewController(controller, animated: true, completion: nil)
}
If your goal is to verify if your application can open a URL, here is what you can do. Although safari can open the URL, the website might not exist or it might be down.
// Swift 5
func verifyUrl (urlString: String?) -> Bool {
if let urlString = urlString {
if let url = NSURL(string: urlString) {
return UIApplication.shared.canOpenURL(url as URL)
}
}
return false
}
As a side note, this does not check whether or not a URL is valid or complete. For example, a call that passes "https://" returns true.
var url:NSURL = NSURL(string: "tel://000000000000")!
if UIApplication.sharedApplication().canOpenURL(url) {
UIApplication.sharedApplication().openURL(url)
} else {
// Put your error handler code...
}
This isn't a regex approach, but it is a naive one that works well for making sure there is a host and an extension if you want a simple and inexpensive approach:
extension String {
var isValidUrlNaive: Bool {
var domain = self
guard domain.count > 2 else {return false}
guard domain.trim().split(" ").count == 1 else {return false}
if self.containsString("?") {
var parts = self.splitWithMax("?", maxSplit: 1)
domain = parts[0]
}
return domain.split(".").count > 1
}
}
Use this only if you want a quick way to check on the client side and you have server logic that will do a more rigorous check before saving the data.
class func verifyUrl (urlString: String?) -> Bool {
//Check for nil
if let urlString = urlString {
// create NSURL instance
if let url = URL(string: urlString) {
// check if your application can open the NSURL instance
return UIApplication.shared.canOpenURL(url)
}
}
return false
}
extension String {
var isValidURL: Bool {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
if let match = detector.firstMatch(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) {
// it is a link, if the match covers the whole string
return match.range.length == self.utf16.count
} else {
return false
}
}
}
Usage:
let string = "https://www.fs.blog/2017/02/naval-ravikant-reading-decision-making/"
if string.isValidURL {
// TODO
}
Reasoning behind using NSDataDetector instead of UIApplication.shared.canOpenURL:
I needed a method that would detect whether the user provided an input that is an URL to something. In many cases, users don't include the http:// nor https:// URL scheme in the URL they type in - e.g., instead of "http://www.google.com" they would type in "www.google.com". Without the URL scheme, the UIApplication.shared.canOpenURL will fail to recognize the URL and will return false. NSDataDetector is, compared to UIApplication.shared.canOpenURL, a rather forgiving tool (as @AmitaiB mentioned in comments) - and it can detect even URLs without the http:// scheme. This way I am able to detect a URL without having to try to add the scheme everytime when testing the string.
Sidenote - SFSafariViewController can open only URLs with http:///https://. Thus, if a detected URL does not have a URL scheme specified, and you want to open the link, you will have to prepend the scheme manually.
This is for latest Swift 4, based on Doug Amos answer (for swift 3)
public static func verifyUrl (urlString: String?) -> Bool {
//Check for nil
if let urlString = urlString {
// create NSURL instance
if let url = NSURL(string: urlString) {
// check if your application can open the NSURL instance
return UIApplication.shared.canOpenURL(url as URL)
}
}
return false
}
This accepted answer doesn't work in my case with wrong url without data https://debug-cdn.checkit4team.com/5/image/Y29tbW9uL2RlZmF1bHRfYXZhdGFyLnBuZw==
So I write extension to solve
extension String {
var verifyUrl: Bool {
get {
let url = URL(string: self)
if url == nil || NSData(contentsOf: url!) == nil {
return false
} else {
return true
}
}
}
}
2020, I was tasked on fixing a bug of a method for underlining string links within a string. Most of the answers here don't work properly (try: aaa.com.bd or aaa.bd) These links should be valid. And then I stumbled upon the regex string for this.
Most of the answer here doesn't address my issue so I'm posting here how I resolved it:
static func isValidDomain(domain: String?) -> Bool {
guard let domain = domain else {
return false
}
// Modified version of https://stackoverflow.com/a/49072718/2304737
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let domainWithScheme = "https://\(domain)"
if let url = URL(string: domainWithScheme),
let match = detector.firstMatch(in: domainWithScheme, options: [], range: NSRange(location: 0, length: domainWithScheme.utf16.count)) {
// it is a link, if the match covers the whole string
return match.range.length == domainWithScheme.utf16.count && url.host != nil
} else {
return false
}
}
What lacks on Milan Nosáľ's answer is, it doesn't address this particular input: