Reputation: 1051
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?
Upvotes: 104
Views: 128147
Reputation: 9662
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
Upvotes: 0
Reputation: 390
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
Upvotes: 0
Reputation: 54
//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
}
Upvotes: 0
Reputation: 3590
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".
Upvotes: 0
Reputation: 2485
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.
Upvotes: 152
Reputation: 319
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)
}
}
Upvotes: 6
Reputation: 13290
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.
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];
}
Upvotes: 14
Reputation: 19757
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.
Upvotes: 106
Reputation: 1790
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!
Upvotes: -2
Reputation: 9586
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)
}
Upvotes: 1
Reputation: 933
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)
}
}
Upvotes: 1
Reputation: 1637
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
}
Upvotes: 2
Reputation: 5655
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.")
}
Upvotes: 16
Reputation: 683
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"
.
Upvotes: 2
Reputation: 658
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
}
Upvotes: 0
Reputation: 63
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
}
Upvotes: 5
Reputation: 11
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
}
Upvotes: 1
Reputation: 4437
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
Upvotes: 7
Reputation: 1308
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
Upvotes: 2
Reputation: 27072
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.
Upvotes: 2
Reputation: 4383
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)
}
Upvotes: 26
Reputation: 1178
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
}
Upvotes: 20
Reputation: 1290
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.
Upvotes: 1
Reputation: 15669
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\\.&%\\$\\-]+)*@)*((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\\.\\,\\?\\'\\\\\\+&%\\$#\\=~_\\-]+))*$"
return self.matches(pattern: urlPattern)
}
}
This way it is also extensible with another use-cases, such as isValidEmail
, isValidName
or whatever your application requires.
Upvotes: 6
Reputation: 2307
var url:NSURL = NSURL(string: "tel://000000000000")!
if UIApplication.sharedApplication().canOpenURL(url) {
UIApplication.sharedApplication().openURL(url)
} else {
// Put your error handler code...
}
Upvotes: 5
Reputation: 12367
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)
}
Upvotes: 2