1

How can you check to see if a URL is valid in Swift 4? I'm building a simple web browser for personal use and even though I know to enter the full URL each time I'd rather get an alert instead of the app crashing if I forget.

import UIKit
import SafariServices

class MainViewController: UIViewController {
    @IBOutlet weak var urlTextField: UITextField!

    @IBAction func startBrowser(_ sender: Any) {
        if let url = self.urlTextField.text {
            let sfViewController = SFSafariViewController(url: NSURL(string: url)! as URL)
            self.present(sfViewController, animated: true, completion: nil)
        }
        print ("Now browsing in SFSafariViewController")
    }
}

For example, if I was to type in a web address without http:// or https:// the app would crash with the error 'NSInvalidArgumentException', reason: 'The specified URL has an unsupported scheme. Only HTTP and HTTPS URLs are supported.'

gzimbric
  • 97
  • 1
  • 1
  • 9

2 Answers2

5

Reading the comments on the accepted answer, I could see that you actually want to validate the URL, to check if it's valid before trying to open with Safari to prevent any crash.

You can use regex to validate the string(I created an extension, so on any string, you can check if it is a valid URL):

extension String {
func validateUrl () -> Bool {
        let urlRegEx = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.\\w{2,3}(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?"
        return NSPredicate(format: "SELF MATCHES %@", urlRegEx).evaluate(with: self)
    }
}
CruzAlex
  • 59
  • 1
  • 1
  • How could I implement this into my function? – gzimbric Jul 18 '18 at 03:40
  • 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 = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.\\w{2,3}(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?" let predicate = NSPredicate(format:"SELF MATCHES %@", argumentArray:[regEx]) return predicate.evaluate(with: string) } – Sheetal Shinde Jul 24 '19 at 08:25
2

You're probably crashing because you're using the ! operator and not checking that it will work. Instead try:

@IBAction func startBrowser(_ sender: Any) {
    if let urlString = self.urlTextField.text {
        let url: URL?
        if urlString.hasPrefix("http://") {
            url = URL(string: urlString)
        } else {
            url = URL(string: "http://" + urlString)
        }
        if let url = url {
            let sfViewController = SFSafariViewController(url: url)
            self.present(sfViewController, animated: true, completion: nil)
            print ("Now browsing in SFSafariViewController")
        }
    }
}

This should give you the idea of how to handle the different cases, but you probably want something more sophisticated which can deal with https and strips whitespace.

Gary Makin
  • 3,109
  • 1
  • 19
  • 27
  • I'm sorry if I didn't word my question correctly. The app crashes every time I don't add http:// to the beginning so I was looking for a fix (Maybe an if/else statement?) – gzimbric Nov 24 '17 at 00:48
  • How about with the change I just made? – Gary Makin Nov 24 '17 at 00:51
  • Yeah, I just fixed that. That's what I get for not trying to compile first. – Gary Makin Nov 24 '17 at 00:54
  • You should add the details of the crash. Where exactly is it? – Gary Makin Nov 24 '17 at 00:58
  • I was able to fix it by removing `URL(string: url_text) ??` from the code – gzimbric Nov 24 '17 at 01:07
  • That change means that you can't paste in a string that has `https://` at the start. I've added an explicit check for this. – Gary Makin Nov 24 '17 at 02:01
  • This code wouldn't compile. What is `starts(with: "http://")` ? String method for that is called `hasPrefix`. And url is optional, checking it is not nil doesn't unwrap it. You should use `if let url = url {` – Leo Dabus Nov 24 '17 at 02:23
  • I don't know why I went with the `!= nil`. I always unwrap properly. I'll fix that. The `starts(with: "http://")` is valid Swift 4 but I should be using `hasPrefix`. Thanks. – Gary Makin Nov 24 '17 at 02:28
  • @GaryMakin thats true `starts(with:)` Is a new sequence instance method available for Xcode 9 or later. – Leo Dabus Nov 24 '17 at 02:36
  • I've been doing too much snake_case programming at work lately. I definitely prefer camelCase. Fixed and gave it a better variable name. – Gary Makin Nov 24 '17 at 02:38
  • Not saying that I would use it in production code but you could check if it hasPrefix "http" only this way it wouldn't add the scheme twice if the string starts with https – Leo Dabus Nov 24 '17 at 02:57
  • @LeoDabus Given that `SFSafariViewController` can only handle http and https, that might be sufficient. But what if the web site was called httphelp.com? I think explicit tests for both would be better. I did try to cover this (in a hand-wavey way) in the last sentence in the answer. – Gary Makin Nov 24 '17 at 03:00