如何检查 Swift 中 URL 的有效性?

How to check validity of URL in Swift?

尝试让应用程序将默认浏览器启动到 URL,但前提是输入的 URL 有效,否则它会显示一条消息,说明 URL 无效。

我将如何使用 Swift 检查有效性?

您可以将 NSURL 类型(其构造函数 returns 是可选类型)与 if-let statement to check the validity of a given URL. In other words, make use of the NSURL failable initializer 结合使用,这是 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)
}

如果您的目标是验证您的应用程序是否可以打开 URL,您可以执行以下操作。尽管 Safari 可以打开 URL,但该网站可能不存在或已关闭。

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

附带说明一下,这 不会 检查 URL 是否有效或完整。例如,传递“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...
}

我发现这个很干净(在 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)
}

用法:

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

===

对于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.")
}

对于已接受答案的 swift 3 版本:

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

或者更快速的解决方案:

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

    return UIApplication.shared.canOpenURL(url)
}

我个人的偏好是通过扩展来解决这个问题,因为我喜欢直接在字符串对象上调用该方法。

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

这样它也可以扩展到另一个 use-cases,例如 isValidEmailisValidName 或您的应用程序需要的任何内容。

这不是一种正则表达式方法,但它是一种天真的方法,如果您想要一种简单且廉价的方法,可以很好地确保有一个主机和一个扩展:

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

仅当您希望在客户端快速检查并且您的服务器逻辑会在保存数据之前进行更严格的检查时才使用此选项。

使用 'canOpenUrl' 对我的用例来说太贵了,我发现这种方法更快

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
}
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。此函数归功于

试试这个:

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

这只是检查有效的 URL 组件以及主机和 url 组件是否为零。此外,您可以将其添加到扩展文件

在某些情况下,检查 url 是否满足 RFC 1808 就足够了。有几种方法可以做到这一点。一个例子:

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

这是因为如果 url 不符合 RFC 1808,.host、.path、.fragment 和其他一些方法将 return 为 nil。

如果不检查,控制台日志中可能会有这样的消息:

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

对于 swift 4 你可以使用:

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
}

Swift 4 使用 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
        }
    }
}

用法:

let string = "https://www.fs.blog/2017/02/naval-ravikant-reading-decision-making/"
if string.isValidURL {
    // TODO
}

使用 NSDataDetector 而不是 UIApplication.shared.canOpenURL 的原因:

我需要一种方法来检测用户是否提供了 URL 的输入。在许多情况下,用户不会在他们输入的 URL 中包含 http://https:// URL 方案 - 例如,他们会输入 "http://www.google.com" 而不是 "http://www.google.com""www.google.com"。如果没有 URL 方案,UIApplication.shared.canOpenURL 将无法识别 URL 并且会 return false。与 UIApplication.shared.canOpenURL 相比,NSDataDetector 是一个相当宽容的工具(正如@AmitaiB 在评论中提到的)——它甚至可以在没有 http:// 方案的情况下检测到 URLs。这样我就可以检测到 URL 而不必在每次测试字符串时都尝试添加方案。

旁注 - SFSafariViewController 只能用 http:///https:// 打开 URLs。因此,如果检测到的 URL 没有指定 URL 方案,而您想打开 link,则必须手动添加方案。

对于Swift4版本

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

这是最新的 Swift 4,基于 Doug Amos回答(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
}

这将 return 作为 URL 有效性的布尔值,如果传递了值为 nil 的可选 URL 则为 nil。

extension URL {

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

}

请注意,如果您打算使用 Safari 视图,则应测试 url.scheme == "http" || url.scheme == "https"

Swift 4.2 优雅URL 构建验证

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

用法

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

Helium不得不应对各种方案:

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

适用于 Swift 4.2 且具有可靠 URL 模式匹配的版本 ...

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

这个被接受的答案在我的情况下不起作用 url 没有数据 https://debug-cdn.checkit4team.com/5/image/Y29tbW9uL2RlZmF1bHRfYXZhdGFyLnBuZw==

所以我写扩展来解决

extension String {
    var verifyUrl: Bool {
        get {
            let url = URL(string: self)

            if url == nil || NSData(contentsOf: url!) == nil {
                return false
            } else {
                return true
            }
        }
    }
}

使用它:

if string. verifyUrl {
    // do something
}

希望对您有所帮助!

2020,我的任务是修复一个在字符串中强调字符串链接的方法的错误。这里的大部分答案都无法正常工作(尝试:aaa.com.bd 或 aaa.bd)这些链接应该有效。然后我偶然发现了正则表达式字符串。

参考:https://urlregex.com/

所以基于那个正则表达式

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

我们可以写一个函数。

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(随便写,类别与否)。

- (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];
}

Swift 5.1 解

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

这里的大部分答案都没有解决我的问题,所以我在这里发布我是如何解决它的:

static func isValidDomain(domain: String?) -> Bool {
    guard let domain = domain else {
        return false
    }
    // Modified version of 
    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
    }
}

的答案缺少的是,它没有解决这个特定的输入:

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

所以我只是添加 host 检查是否存在和计划外的“URL”。