105

Trying to make an app launch the default browser to a URL, but only if the URL entered is valid, otherwise it displays a message saying the URL is invalid.

How would I go about checking the validity using Swift?

2
  • send an NSURLRequest and check the return? Commented Jan 22, 2015 at 0:02
  • Use the string to form an NSURL and see if it's nil? Commented Jan 22, 2015 at 0:14

26 Answers 26

153

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.

Sign up to request clarification or add additional context in comments.

8 Comments

if let urlString = urlString, url = NSURL(string: urlString) where UIApplication.sharedApplication().canOpenURL(url) { return true }
didn't work when I test on swift2.0 if let url = NSURL(string: urlString) {
@VijaySinghRana That is a valid NSURL, this function will not check if the url exists. Noted in answer: "If your goal is to verify if your application can open a url here is what you can do".
This method prints this log " -canOpenURL: failed for URL: " in debugger if URL is invalid. how to remove this?
Remember this fails for urls which don't start with protocol:// like http:// or https://
|
107

Swift 4 elegant solution using NSDataDetector:

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.

10 Comments

Just not that NSDataDetector is a rather forgiving detection tool. Consider: adobe.com/go/flex *isValidURL? true *UIApplication.shared.canOpenURL? false gskinner.com/products/spl *iVU? T *canOpen? F https://google.com *iVU? T *canOpen? T https:google.com *iVU? T *canOpen? T www.cool.com.au *iVU? T *canOpen? F http://www.cool.com.au *iVU? T *canOpen? T http://www.cool.com.au/ersdfs *iVU? T *canOpen? T http://www.cool.com.au/ersdfs?dfd=dfgd@s=1 *iVU? T *canOpen? T http://www.cool.com:81/index.html *iVU? T *canOpen? T
@AmitaiB I am using NSDataDetector because the UIApplication.shared.canOpenURL returns false with many valid URLs (which can be open in SafariViewController). I needed to highlight URLs in text and then allow the user to open them in in safari view controller - that's why I ended up with this solution
Ah, that makes sense.
I think canOpen returns true exactly when the string starts with http:// (or any other valid protocol)
@amsmath maybe, but many users don’t include protocol in the URL, and I needed to detect URLs in the user’s input text. I see I must update the answer with the reasoning for the dataDetector
|
26

For a swift 3 version of the accepted answer:

func verifyUrl(urlString: String?) -> Bool {
    if let urlString = urlString {
        if let url = URL(string: urlString) {
            return UIApplication.shared.canOpenURL(url)
        }
    }
    return false
}

Or for a more Swifty solution:

func verifyUrl(urlString: String?) -> Bool {
    guard let urlString = urlString,
          let url = URL(string: urlString) else { 
        return false 
    }

    return UIApplication.shared.canOpenURL(url)
}

3 Comments

While it is faithful to the previous version, you could combine the if let..s into a single check, and then also flip the if to a guard ... else { return false } to make it clearer that return false is a failure state and return UIApplication... is a (possible) success state.
@ReactiveRaven Yep I agree. I've added a version as suggested
this add logs for each failed url, which is a bit annoying
20

Using 'canOpenUrl' was too expensive for my use case, I found this approach to be quicker

func isStringLink(string: String) -> Bool {
    let types: NSTextCheckingResult.CheckingType = [.link]
    let detector = try? NSDataDetector(types: types.rawValue)
    guard (detector != nil && string.characters.count > 0) else { return false }
    if detector!.numberOfMatches(in: string, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, string.characters.count)) > 0 {
        return true
    }
    return false
}

2 Comments

This was perfect for me to use in an extension where UIApplication.shared isn't available. Thanks!
For Swift 4: string.characters.count becomes string.count
16

I found this one clean (In Swift):

func canOpenURL(string: String?) -> Bool {
    guard let urlString = string else {return false}
    guard let url = NSURL(string: urlString) else {return false}
    if !UIApplication.sharedApplication().canOpenURL(url) {return false}

    //
    let regEx = "((https|http)://)((\\w|-)+)(([.]|[/])((\\w|-)+))+"
    let predicate = NSPredicate(format:"SELF MATCHES %@", argumentArray:[regEx])
    return predicate.evaluateWithObject(string)
}

Usage:

if canOpenURL("abc") {
    print("valid url.")
} else {
    print("invalid url.")
}

===

for Swift 4.1:

func canOpenURL(_ string: String?) -> Bool {
    guard let urlString = string,
        let url = URL(string: urlString)
        else { return false }

    if !UIApplication.shared.canOpenURL(url) { return false }

    let regEx = "((https|http)://)((\\w|-)+)(([.]|[/])((\\w|-)+))+"
    let predicate = NSPredicate(format:"SELF MATCHES %@", argumentArray:[regEx])
    return predicate.evaluate(with: string)
}

// Usage
if canOpenURL("abc") {
    print("valid url.")
} else {
    print("invalid url.") // This line executes
}

if canOpenURL("https://www.google.com") {
    print("valid url.") // This line executes
} else {
    print("invalid url.")
}

3 Comments

not working for me for url like https: //abc.com/filename.html?utm_source=xyzconnect&utm_medium=service Its a dummy url, and space at beginning , as can not paste actual url.
Yes, the above logic just validates, it won't transform the original string!
@Ashok What Rohit meant is he added a space so you could read. The regex above is invalid I believe as it's not handling parameters and/or enforcing unnecessary subdomain, which is not a requirement, thereby making many valid urls invalid. eg. example.com/…
14

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.

Ref: https://urlregex.com/

So based on that regex

"((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.\\w{2,3}(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?"

We can write a function.

SWIFT 5.x:

extension String {
    var validURL: Bool {
        get {
            let regEx = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.\\w{2,3}(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?"
            let predicate = NSPredicate(format: "SELF MATCHES %@", argumentArray: [regEx])
            return predicate.evaluate(with: self)
        }
    }
}

OBJECTIVE-C (write this however you want, category or not).

- (BOOL)stringIsValidURL:(NSString *)string
{
    NSString *regEx = @"((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.\\w{2,3}(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@" argumentArray:@[regEx]];
    return [predicate evaluateWithObject:string];
}

2 Comments

Nice solution and using, however I notice it fails due to case sensitivity: Https://google.com or computerhope.com/JARGON.HTM as JARGON.HTM and jargon.htm are two different urls. However, it can be solved by just converting the string to lower case before testing.
Doesn't work for me (Swift 5.5.2): My URL looks like this: https://you.test.com/0003-example/
7

In some cases it can be enough to check that the url satisfies RFC 1808. There are several ways to do this. One example:

if let url = URL(string: urlString), url.host != nil {
  // This is a correct url
}

This is because .host, as well as .path, .fragment and a few other methods would return nil if url doesn't conform to RFC 1808.

If you don't check, you might have this kind of messages in console log:

Task <DF46917D-1A04-4E76-B54E-876423224DF7>.<72> finished with error - code: -1002

1 Comment

This should be the accepted answer IMO. It is by far the simplest and most elegant and there's no need to mess about with fragile regexes or use expensive UIApplication calls.
6

My personal preference is to approach this with an extension, because I like to call the method directly on the string object.

extension String {

    private func matches(pattern: String) -> Bool {
        let regex = try! NSRegularExpression(
            pattern: pattern,
            options: [.caseInsensitive])
        return regex.firstMatch(
            in: self,
            options: [],
            range: NSRange(location: 0, length: utf16.count)) != nil
    }

    func isValidURL() -> Bool {
        guard let url = URL(string: self) else { return false }
        if !UIApplication.shared.canOpenURL(url) {
            return false
        }

        let urlPattern = "^(http|https|ftp)\\://([a-zA-Z0-9\\.\\-]+(\\:[a-zA-Z0-9\\.&amp;%\\$\\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\\-]+\\.)*[a-zA-Z0-9\\-]+\\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\?\\'\\\\\\+&amp;%\\$#\\=~_\\-]+))*$"
        return self.matches(pattern: urlPattern)
    }
}

This way it is also extensible with another use-cases, such as isValidEmail, isValidName or whatever your application requires.

2 Comments

This doesn't seem to work on google.com. It only takes something like google.com
@lwdthe1 yeah, the regular expression has some holes. The point being made though, is the architecture of the solution (approaching it with extension). Feel absolutely free to replace the regex with your own in your app. Or if you have a better one - improve my answer! :)
6

Swift 5.1 Solution

extension String {
    func canOpenUrl() -> Bool {
        guard let url = URL(string: self), UIApplication.shared.canOpenURL(url) else { return false }
        let regEx = "((https|http)://)((\\w|-)+)(([.]|[/])((\\w|-)+))+"
        let predicate = NSPredicate(format:"SELF MATCHES %@", argumentArray:[regEx])
        return predicate.evaluate(with: self)
    }
}

1 Comment

Warning, it doesn't work with query parameters (?key=value).
5
var url:NSURL = NSURL(string: "tel://000000000000")!
if UIApplication.sharedApplication().canOpenURL(url) {
    UIApplication.sharedApplication().openURL(url)
} else {
     // Put your error handler code...
}

Comments

5

For Swift 4 version

static func isValidUrl (urlString: String?) -> Bool {
    if let urlString = urlString {
        if let url = URL(string: urlString) {
            return UIApplication.shared.canOpenURL(url)
        }
    }
    return false
}

Comments

2

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 NSURL failable 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)
}

Comments

2
extension String {    
    func isStringLink() -> Bool {
        let types: NSTextCheckingResult.CheckingType = [.link]
        let detector = try? NSDataDetector(types: types.rawValue)
        guard (detector != nil && self.characters.count > 0) else { return false }
        if detector!.numberOfMatches(in: self, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count)) > 0 {
            return true
        }
        return false
    }
}

//Usage
let testURL: String = "http://www.google.com"
if testURL.isStringLink() {
    //Valid!
} else {
    //Not valid.
}

It's advised to use this check only once and then reuse.

P.S. Credits to Shachar for this function.

Comments

2

Try this:

func isValid(urlString: String) -> Bool
{
    if let urlComponents = URLComponents.init(string: urlString), urlComponents.host != nil, urlComponents.url != nil
    {
        return true
    }
    return false
}

This simply checks for valid URL components and if the host and url components are not nil. Also, you can just add this to an extensions file

Comments

2

This will return a boolean for a URL's validity, or nil if an optional URL with a value of nil is passed.

extension URL {

    var isValid: Bool {
        get {
            return UIApplication.shared.canOpenURL(self)
        }
    }

}

Note that, if you plan to use a Safari view, you should test url.scheme == "http" || url.scheme == "https".

Comments

2

Swift 4.2 Elegant URL construction with verification

import Foundation
import UIKit

extension URL {

  init?(withCheck string: String?) {
    let regEx = "((https|http)://)((\\w|-)+)(([.]|[/])((\\w|-)+))+"
    guard
        let urlString = string,
        let url = URL(string: urlString),
        NSPredicate(format: "SELF MATCHES %@", argumentArray: [regEx]).evaluate(with: urlString),
        UIApplication.shared.canOpenURL(url)
        else {
            return nil
    }

    self = url
  }
}

Usage

var imageUrl: URL? {
    if let url = URL(withCheck: imageString) {
        return url
    }
    if let url = URL(withCheck: image2String) {
        return url
    }
    return nil
}

Comments

1

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.

Comments

1

For swift 4 you can use:

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
}

Comments

1

Helium having to deal with various schemes:

struct UrlHelpers {
    // Prepends `http://` if scheme isn't `https?://` unless "file://"
    static func ensureScheme(_ urlString: String) -> String {
        if !(urlString.lowercased().hasPrefix("http://") || urlString.lowercased().hasPrefix("https://")) {
            return urlString.hasPrefix("file://") ? urlString : "http://" + urlString
        } else {
            return urlString
        }
    }

    // https://mathiasbynens.be/demo/url-regex
    static func isValid(urlString: String) -> Bool {
        // swiftlint:disable:next force_try
        if urlString.lowercased().hasPrefix("file:"), let url = URL.init(string: urlString) {
            return FileManager.default.fileExists(atPath:url.path)
        }

        let regex = try! NSRegularExpression(pattern: "^(https?://)[^\\s/$.?#].[^\\s]*$")
        return (regex.firstMatch(in: urlString, range: urlString.nsrange) != nil)
    }
}

Comments

1

Version that works with Swift 4.2 and has reliable URL pattern matching ...

func matches(pattern: String) -> Bool
{
    do
    {
        let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive])
        return regex.firstMatch(in: self, options: [], range: NSRange(location: 0, length: utf16.count)) != nil
    }
    catch
    {
        return false
    }
}


func isValidURL() -> Bool
{
    guard let url = URL(string: self) else { return false }
    if !UIApplication.shared.canOpenURL(url) { return false }

    let urlPattern = "(http|ftp|https):\\/\\/([\\w+?\\.\\w+])+([a-zA-Z0-9\\~\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)_\\-\\=\\+\\\\\\/\\?\\.\\:\\;\\'\\,]*)?"
    return self.matches(pattern: urlPattern)
}

1 Comment

Nope.This function says "aaa.com" is invalid.
0

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
}

Comments

0

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:

https://#view-?name=post&post.post_id=519&message.message_id=349

So I just add host check existence and unschemed "URL".

Comments

0
//Add in "extension String"
func verifyUrl()  -> Bool {
    var newString = self
    let index = self.index(self.startIndex, offsetBy: 4)
    let mySubstring = self.prefix(upTo: index)
    if mySubstring == "www." {
        newString = "https://" + newString
    }
    if let url = NSURL(string: newString) {
        return UIApplication.shared.canOpenURL(url as URL)
    }
    return false
}

1 Comment

NOTE: I'm commenting via "Late Answer" triage. I was not a fan of the way the question was asked (What makes a URL valid or invalid, you gotta be specific), otherwise, why not just look at the URL framework's error message...). In regards to your answer, I think it would improved by adding comments that say specifically what you are teasing and why. For example, is https://localhost invalid, and if so, why?
0
extension String {
  var isValidUrl: Bool {
    if let url = NSURL(string: self) {
      return UIApplication.shared.canOpenURL(url as URL)
    }
    return false
  }
}

Usage:

let test1 = "abc"
let isValidUrl = test1.isValidUrl

// Result of isValidUrl will be False


let test1 = "https://www.google.com/"
let isValidUrl = test1.isValidUrl

// Result of isValidUrl will be True

Comments

0

Swift 5, a bit performant NSDataDetector approach (does quick return if string is empty)

extension String {

    var isValidURL: Bool {
        guard !isEmpty, !trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
            return false
        }

        do {
            let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
            let matches = detector.matches(in: self, 
                                           options: [],
                                           range: NSRange(location: .zero, length: utf16.count))
            return !matches.isEmpty
        } catch {
            return false
        }
    }
}

// Test
let urlString1 = "https://you.test.com/0003-example?param=value"
let urlString2 = "www.apple.com"
let urlString3 = "http://example.com"
let urlString4 = "ftp://ftp.example.com/file.txt"
let urlString5 = ""
let urlString6 = "  "

urlString1.isValidURL // true
urlString2.isValidURL // true
urlString3.isValidURL // true
urlString4.isValidURL // true
urlString5.isValidURL // false
urlString6.isValidURL // false

1 Comment

Can you clarify how this answer improves on the 3 or 4 other answers that already use NSDataDetector?
-2

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
            }
        }
    }
}

Use it:

if string. verifyUrl {
    // do something
}

Hope this help!

4 Comments

This might be working in your case, but only when network connetction is available. Also I highly discourage you from calling a synchronous network request just to verify the URL is valid. On top of that it is not obvious from the variable definition that it performs synchronous long running operation, which might cause you a lot of trouble in future when used from main thread. Please read developer.apple.com/documentation/foundation/nsdata/…
@5keeve In which part of the code where a network request is being made?
@CyberMew ` NSData(contentsOf: url!)` makes a synchronous (blocking) request, it is supposed to be used for (small) local files, but when you give it network-based URL it does it too. I mentioned it, because the URL mentioned in answer is network-based. There is also yellow Important box in the documentation I mentioned, go and read it ;)
Ah ok thanks for the information, learnt something new today!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.