如何在iPhone上验证URL

90
在我正在开发的iPhone应用程序中,有一个设置可以输入URL,由于形式和功能,这个URL需要在线上和离线上进行验证。
到目前为止,我还没有找到任何验证URL的方法,所以问题是:
如何在iPhone(Objective-C)上在线上和离线上验证URL输入?

阅读他的回答中的评论,验证未能正确工作。 - Thizzer
22个回答

239

为什么不直接依赖于Foundation.framework呢?

它能够胜任这个任务而且不需要使用RegexKit

NSURL *candidateURL = [NSURL URLWithString:candidate];
// WARNING > "test" is an URL according to RFCs, being just a path
// so you still should check scheme and all other NSURL attributes you need
if (candidateURL && candidateURL.scheme && candidateURL.host) {
  // candidate is a well-formed url with:
  //  - a scheme (like http://)
  //  - a host (like stackoverflow.com)
}

根据苹果官方文档:

URLWithString: 创建并返回一个使用提供的字符串初始化的NSURL对象。

+ (id)URLWithString:(NSString *)URLString

参数

URLString :用于初始化NSURL对象的字符串。必须符合RFC 2396标准。此方法根据RFC 1738和1808解析URLString。

返回值

使用URLString初始化的NSURL对象。如果字符串格式不正确,则返回nil。


1
@MrThys - 有没有可能提供一些无法捕获的格式错误的URL示例? 很想知道..到目前为止似乎是一个很好的解决方案。 - uɥƃnɐʌuop
7
这个URL http://www.aol.comhttp://www.nytimes.com 通过了这个测试。 - Aaron Brager
1
代码不会检查格式错误的URL。例如:<code>http://afasd</code>,该URL仍然会通过测试。 - denil
39
文档有误。写一些测试代码——当我传入字符串@"#@#@$##%$#$#"或者@"tp:/fdfdfsfdsf"时,NSURL并不会返回nil。因此这个方法对于检查有效的HTTP URL等将是无用的。 - Tony Arnold
1
这似乎适用于@TonyArnold指出的测试用例(iOS 9.1 SDK),但对于www.microsoft.com失败,因为主机为空。如果我做http//www.microsoft.com或http:// microsoft.com,则通过验证。对于普通人来说,www.microsoft.com是一个合适的URL,所以在我的情况下,我必须调整此解决方案。编辑:在http://后添加“ ”,因为SO自动将其超链接。 - newDeveloper
显示剩余11条评论

100

感谢这篇文章,你可以避免使用RegexKit。这是我的解决方案(适用于iOS > 3.0的iphone开发):

- (BOOL) validateUrl: (NSString *) candidate {
    NSString *urlRegEx =
    @"(http|https)://((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+";
    NSPredicate *urlTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", urlRegEx]; 
    return [urlTest evaluateWithObject:candidate];
}

如果你想在Swift中进行检查,可以使用我下面给出的解决方案:

 func isValidUrl(url: String) -> Bool {
        let urlRegEx = "^(https?://)?(www\\.)?([-a-z0-9]{1,63}\\.)*?[a-z0-9][-a-z0-9]{0,61}[a-z0-9]\\.[a-z]{2,6}(/[-\\w@\\+\\.~#\\?&/=%]*)?$"
        let urlTest = NSPredicate(format:"SELF MATCHES %@", urlRegEx)
        let result = urlTest.evaluate(with: url)
        return result
    }

5
这是需要翻译的内容:((http|https)://)?((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+翻译后的内容为:这段代码用于匹配网址,其中"http://" 或 "https://" 为可选项。 - Yeung
无法在google.com、www.google.com以及//www.google.com上工作。 - Revinder
以下网址在我的情况下无法使用。 http://money.cnn.com/2015/10/19/technology/apple-app-store/index.html?category=home - DJtiwari
@DJtiwari +1 是的,它不起作用了。你找到任何解决方法了吗? - Hamza MHIRA
显示剩余3条评论

32

不要自己编写正则表达式,可以依赖于苹果的正则表达式。我一直在使用一个基于NSString类别的方法,利用NSDataDetector来检测字符串中是否存在链接。如果由NSDataDetector发现链接的范围与整个字符串的长度相等,则为有效的URL。

- (BOOL)isValidURL {
    NSUInteger length = [self length];
    // Empty strings should return NO
    if (length > 0) {
        NSError *error = nil;
        NSDataDetector *dataDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error];
        if (dataDetector && !error) {
            NSRange range = NSMakeRange(0, length);
            NSRange notFoundRange = (NSRange){NSNotFound, 0};
            NSRange linkRange = [dataDetector rangeOfFirstMatchInString:self options:0 range:range];
            if (!NSEqualRanges(notFoundRange, linkRange) && NSEqualRanges(range, linkRange)) {
                return YES;
            }
        }
        else {
            NSLog(@"Could not create link data detector: %@ %@", [error localizedDescription], [error userInfo]);
        }
    }
    return NO;
}

非常聪明。真正的工程师。{你知道吗,我遇到了一个奇怪的问题,如果我直接发送字符串“<null>”,它就会崩溃!我一直都搞不清楚原因。} - Fattie
啊 - 在儿子中是"<null>",但苹果有用地提供了NSNull! :O - Fattie
我创建了一个Gist,在其中开始添加此片段的测试用例。请随意添加更多。https://gist.github.com/b35097bad451c59e23b1.git - Yevhen Dubinin

26

我的解决方案使用Swift

func validateUrl (stringURL : NSString) -> Bool {

    var urlRegEx = "((https|http)://)((\\w|-)+)(([.]|[/])((\\w|-)+))+"
    let predicate = NSPredicate(format:"SELF MATCHES %@", argumentArray:[urlRegEx])
    var urlTest = NSPredicate.predicateWithSubstitutionVariables(predicate)

    return predicate.evaluateWithObject(stringURL)
}

测试用例:

var boolean1 = validateUrl("http.s://www.gmail.com")
var boolean2 = validateUrl("https:.//gmailcom")
var boolean3 = validateUrl("https://gmail.me.")
var boolean4 = validateUrl("https://www.gmail.me.com.com.com.com")
var boolean6 = validateUrl("http:/./ww-w.wowone.com")
var boolean7 = validateUrl("http://.www.wowone")
var boolean8 = validateUrl("http://www.wow-one.com")
var boolean9 = validateUrl("http://www.wow_one.com")
var boolean10 = validateUrl("http://.")
var boolean11 = validateUrl("http://")
var boolean12 = validateUrl("http://k")

结果:

false
false
false
true
false
false
true
true
false
false
false

10

使用这个 -

NSString *urlRegEx = @"http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&amp;=]*)?";

1
我只是为了ASP.NET正则表达式验证器简单地复制了它 ;) - Vaibhav Saran
完美,唯一的问题是它无法识别 www.google.com/+gplusname - MuhammadBassio

5

我使用RegexKit解决了这个问题,并构建了一个快速的正则表达式来验证URL;

NSString *regexString = @"(http|https)://((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+";
NSString *subjectString = brandLink.text;
NSString *matchedString = [subjectString stringByMatching:regexString];

然后我检查匹配的字符串是否等于主题字符串,如果是这样,那么URL就是有效的 :)

如果我的正则表达式有误,请纠正我 ;)


我可能错了,但我认为正则表达式无法验证带查询字符串或命名锚点的URL。 - hpique
你可以通过将 (http|https):// 替换为 ((http|https)://)* 来使前缀部分变为可选,但这样会允许非常广泛的 URL。 - Thizzer

4
我发现最简单的方法是这样的:
- (BOOL)validateUrl: (NSURL *)candidate
{
    NSURLRequest *req = [NSURLRequest requestWithURL:candidate];
    return [NSURLConnection canHandleRequest:req];
}

我已经使用它一段时间了,似乎运行正常。它还似乎与新的顶级域名(https://www.namecheap.com/domains/new-tlds/explore.aspx)很兼容。 - julianwyz
这是毫无意义的。如果您的URL是无效字符串,在创建NSURL时它将崩溃,因此可以检查它,而不是这样做。即使如此,它仍然使用旧的API。 - Legoless
@Legoless 可以在它周围使用 try...catch 吗? - Brett
@Legoless 你使用哪个版本?这个解决方案在我的情况下运作良好。 - Michael

4

有趣的是,我在这里并没有找到一个非常简单的解决方案,但是它仍然可以处理http / https链接。

请记住,这不是一个完美的解决方案,但它适用于以下情况。总之,正则表达式测试URL是否以http://https://开头,然后检查至少1个字符,然后检查一个点,然后再次检查至少1个字符。不允许有空格。

+ (BOOL)validateLink:(NSString *)link
{
    NSString *regex = @"(?i)(http|https)(:\\/\\/)([^ .]+)(\\.)([^ \n]+)";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
    return [predicate evaluateWithObject:link];
}

已针对以下网址通过验证:

@"HTTP://FOO.COM",
@"HTTPS://FOO.COM",
@"http://foo.com/blah_blah",
@"http://foo.com/blah_blah/",
@"http://foo.com/blah_blah_(wikipedia)",
@"http://foo.com/blah_blah_(wikipedia)_(again)",
@"http://www.example.com/wpstyle/?p=364",
@"https://www.example.com/foo/?bar=baz&inga=42&quux",
@"http://✪df.ws/123",
@"http://userid:password@example.com:8080",
@"http://userid:password@example.com:8080/",
@"http://userid@example.com",
@"http://userid@example.com/",
@"http://userid@example.com:8080",
@"http://userid@example.com:8080/",
@"http://userid:password@example.com",
@"http://userid:password@example.com/",
@"http://142.42.1.1/",
@"http://142.42.1.1:8080/",
@"http://➡.ws/䨹",
@"http://⌘.ws",
@"http://⌘.ws/",
@"http://foo.com/blah_(wikipedia)#cite-",
@"http://foo.com/blah_(wikipedia)_blah#cite-",
@"http://foo.com/unicode_(✪)_in_parens",
@"http://foo.com/(something)?after=parens",
@"http://☺.damowmow.com/",
@"http://code.google.com/events/#&product=browser",
@"http://j.mp",
@"http://foo.bar/?q=Test%20URL-encoded%20stuff",
@"http://مثال.إختبار",
@"http://例子.测试",
@"http://उदाहरण.परीक्षा",
@"http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com",
@"http://1337.net",
@"http://a.b-c.de",
@"http://223.255.255.254"

以下 URL 已被测试为无效:

@"",
@"foo",
@"ftp://foo.com",
@"ftp://foo.com",
@"http://..",
@"http://..",
@"http://../",
@"//",
@"///",
@"http://##/",
@"http://.www.foo.bar./",
@"rdar://1234",
@"http://foo.bar?q=Spaces should be encoded",
@"http:// shouldfail.com",
@":// should fail"

网址来源: https://mathiasbynens.be/demo/url-regex


3

如果您不想使用 httphttpswww,您可以使用此选项。

NSString *urlRegEx = @"^(http(s)?://)?((www)?\.)?[\w]+\.[\w]+";

示例

- (void) testUrl:(NSString *)urlString{
    NSLog(@"%@: %@", ([self isValidUrl:urlString] ? @"VALID" : @"INVALID"), urlString);
}

- (void)doTestUrls{
    [self testUrl:@"google"];
    [self testUrl:@"google.de"];
    [self testUrl:@"www.google.de"];
    [self testUrl:@"http://www.google.de"];
    [self testUrl:@"http://google.de"];
}

输出:

INVALID: google
VALID: google.de
VALID: www.google.de
VALID: http://www.google.de
VALID: http://google.de

这看起来非常有趣。它是100%防弹的吗? - Supertecnoboff

3
Lefakir的解决方案存在一个问题。 他的正则表达式无法匹配 "http://instagram.com/p/4Mz3dTJ-ra/" 这样的网址组件,因为它包含了数字和字面字符。他的正则表达式不能匹配这样的网址。 以下是我的改进。
"(http|https)://((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*)+)+(/)?(\\?.*)?"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接