【问题标题】:How to check validity of URL in Swift?如何在 Swift 中检查 URL 的有效性?
【发布时间】:2015-03-20 16:12:15
【问题描述】:

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

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

【问题讨论】:

  • 发送一个 NSURLRequest 并检查返回?
  • 用字符串组成一个NSURL,看看是不是nil?

标签: url swift ios8


【解决方案1】:

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

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

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

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

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

【讨论】:

    【解决方案2】:

    如果您的目标是验证您的应用程序是否可以打开 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
    }
    

    附带说明,这检查网址是否有效或完整。例如,传递“https://”的调用返回 true。

    【讨论】:

    • 如果让 urlString = urlString, url = NSURL(string: urlString) where UIApplication.sharedApplication().canOpenURL(url) { return true }
    • 当我在 swift2.0 if let url = NSURL(string: urlString) {987654322@ 上测试时没有工作
    • @VijaySinghRana 那是一个有效的 NSURL,这个函数不会检查 url 是否存在。回答中指出:“如果您的目标是验证您的应用程序是否可以在此处打开一个 url,那么您可以做什么”。
    • 如果 URL 无效,此方法会在调试器中打印此日志“-canOpenURL: failed for URL:”。这个怎么去掉?
    • 请记住,对于不以 protocol:// 开头的网址(例如 http:// 或 https://
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 不错的解决方案和使用,但是我注意到它由于区分大小写而失败:Https://google.comcomputerhope.com/JARGON.HTM 因为 JARGON.HTM 和 jargon.htm 是两个不同的 URL。但是,可以通过在测试前将字符串转换为小写来解决。
      【解决方案5】:

      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 方案 - 例如,他们会输入 "www.google.com" 而不是 "http://www.google.com"。如果没有 URL 方案,UIApplication.shared.canOpenURL 将无法识别 URL,并将返回 false。与UIApplication.shared.canOpenURL 相比,NSDataDetector 是一个相当宽容的工具(正如 cmets 中提到的@AmitaiB)——它甚至可以检测没有http:// 方案的 URL。这样我就可以检测到一个 URL,而不必在每次测试字符串时都尝试添加方案。

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

      【讨论】:

      • 并不是说NSDataDetector 是一个相当宽容的检测工具。考虑: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 我正在使用NSDataDetector,因为UIApplication.shared.canOpenURL 返回false 并带有许多有效的URL(可以在SafariViewController 中打开)。我需要突出显示文本中的 URL,然后允许用户在 Safari 视图控制器中打开它们——这就是我最终得到这个解决方案的原因
      • 啊,有道理。
      • 我认为当字符串以 http://(或任何其他有效协议)开头时,canOpen 会返回 true
      • @amsmath 可能,但是很多用户的 URL 中没有包含协议,我需要检测用户输入文本中的 URL。我知道我必须用 dataDetector 的推理来更新答案
      【解决方案6】:

      这个接受的答案在我的情况下不起作用,没有数据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
      }
      

      希望对您有所帮助!

      【讨论】:

      • 这可能适用于您的情况,但仅当网络连接可用时。此外,我强烈建议您不要仅仅为了验证 URL 有效而调用同步网络请求。最重要的是,从变量定义来看,它执行同步的长时间运行操作并不明显,这可能会在将来从主线程中使用时给您带来很多麻烦。请阅读developer.apple.com/documentation/foundation/nsdata/…
      • @5keeve 在代码的哪一部分发出网络请求?
      • @Cyber​​Mew `NSData(contentsOf: url!)` 发出一个同步(阻塞)请求,它应该用于(小)本地文件,但是当你给它基于网络的 URL 时也这样做。我提到了它,因为答案中提到的 URL 是基于网络的。我提到的文档中还有黄色的重要框,去阅读它;)
      • 好的,谢谢你的信息,今天学到了新东西!
      【解决方案7】:

      适用于 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)
      }
      

      【讨论】:

      • 不行。这个函数说“aaa.com”无效。
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

        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
        }
        

        【讨论】:

          【解决方案10】:

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

          【讨论】:

          • 不错的解决方案!谢谢!
          • 不适用于我的网址,例如 https://abc.com/filename.html?utm_source=xyzconnect&utm_medium=service 它是一个虚拟网址,开头有空格,因为无法粘贴实际网址。
          • 是的,上面的逻辑只是验证,它不会转换原始字符串!
          • @Ashok Rohit 的意思是他添加了一个空格,以便您可以阅读。我认为上面的正则表达式是无效的,因为它没有处理参数和/或强制执行不必要的子域,这不是必需的,从而使许多有效的 url 无效。例如。 example.com/…
          【解决方案11】:

          这将返回一个表示 URL 有效性的布尔值,如果传递了一个值为 nil 的可选 URL,则返回 nil。

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

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

          【讨论】:

            【解决方案12】:

            这是最新的 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
            }
            

            【讨论】:

              【解决方案13】:

              对于 Swift 4 版本

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

              【讨论】:

                【解决方案14】:

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

                【讨论】:

                  【解决方案15】:

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

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

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

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

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

                  【讨论】:

                    【解决方案16】:

                    试试这个:

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

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

                    【讨论】:

                      【解决方案17】:
                      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.
                      }
                      

                      建议仅使用此检查一次,然后重复使用。

                      附注感谢 Shachar 提供此功能。

                      【讨论】:

                        【解决方案18】:

                        对于已接受答案的 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)
                        }
                        

                        【讨论】:

                        • 虽然它忠实于以前的版本,但您可以将if let..s 合并为一个检查,然后还将if 翻转为guard ... else { return false },以使@987654326 更清晰@ 是失败状态,return UIApplication... 是(可能的)成功状态。
                        • @ReactiveRaven 是的,我同意。我按照建议添加了一个版本
                        【解决方案19】:

                        使用“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
                        }
                        

                        【讨论】:

                        • 这非常适合我在 UIApplication.shared 不可用的扩展中使用。谢谢!
                        • 对于 Swift 4:string.characters.count 变为 string.count
                        【解决方案20】:

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

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

                        仅当您想要一种快速检查客户端的方法并且您的服务器逻辑将在保存数据之前进行更严格的检查时才使用此选项。

                        【讨论】:

                          【解决方案21】:

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

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

                          这样,它也可以通过其他用例进行扩展,例如 isValidEmailisValidName 或您的应用程序需要的任何东西。

                          【讨论】:

                          • 这似乎不适用于 google.com。它只需要google.com
                          • @lwdthe1 是的,正则表达式有一些漏洞。不过,重点是解决方案的架构(通过扩展来接近它)。完全可以在您的应用程序中用您自己的正则表达式替换。或者,如果你有更好的 - 改进我的答案! :)
                          【解决方案22】:
                          var url:NSURL = NSURL(string: "tel://000000000000")!
                          if UIApplication.sharedApplication().canOpenURL(url) {
                              UIApplication.sharedApplication().openURL(url)
                          } else {
                               // Put your error handler code...
                          }
                          

                          【讨论】:

                            【解决方案23】:

                            您可以将NSURL 类型(其构造函数返回一个可选类型)与if-let statement 结合使用来检查给定URL 的有效性。换句话说,利用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)
                            }
                            

                            【讨论】:

                              猜你喜欢
                              • 2019-10-28
                              • 2021-03-21
                              • 1970-01-01
                              • 1970-01-01
                              • 2011-04-17
                              • 1970-01-01
                              • 2011-01-14
                              • 2022-11-19
                              相关资源
                              最近更新 更多