如何从一个NSString句子中提取URL?

25

我想实现的目标如下。我有一个NSString,其中包含一个句子和一个URL。我需要能够从任何NSString中提取出所呈现的URL。例如:

假设我有这个NSString:

NSString *someString = @"This is a sample of a http://example.com/efg.php?EFAei687e3EsA sentence with a URL within it.";
我需要能够从这个NSString中提取http://example.com/efg.php?EFAei687e3EsA。这个NSString不是静态的,结构会发生变化,URL也不一定在句子的同一位置。我尝试过查看three20代码,但对我来说毫无意义。还有其他方法可以完成这个任务吗?

查看我的回答,这是一个类似的问题,采用了Swift 3,并给出了2种解决方法。 - Imanou Petit
9个回答

92
使用 NSDataDetector
NSString *string = @"This is a sample of a http://example.com/efg.php?EFAei687e3EsA sentence with a URL within it.";
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])];
for (NSTextCheckingResult *match in matches) {
  if ([match resultType] == NSTextCheckingTypeLink) {
    NSURL *url = [match URL];
    NSLog(@"found URL: %@", url);
  }
}

这样,您就不必依赖不可靠的正则表达式,而且当苹果升级其链接检测代码时,您可以免费获得这些改进。


1
+1 太棒了!!!我不知道NSDataDetector有一个链接检测代码。好吧,我刚学到了新东西。谢谢Dave,我将测试正则表达式和你的NSDataDector代码,看哪个最适合我的需求。然而,我同意你关于使用苹果的代码而不是不可靠的代码的观点。再次感谢。我可能需要改变我的答案。 :-) - 0SX
1
@0SX 不客气。只是需要注意:这仅适用于iOS 4及以上版本。 - Dave DeLong
谢谢 Dave。你会如何提取那个链接?使用 stringByReplacingOccurrencesOfString 是最好的方法吗? - marciokoko
@marciokoko NSTextCheckingResult 可以告诉您匹配子字符串的范围,然后您可以将其与 substringWithRange: 一起使用。 - Dave DeLong
+1,但是为什么我会收到隐式转换类型警告?它说我正在将NSTextCheckingType枚举转换为NSTextCheckingTypes枚举。这是怎么回事? - Chase Roberts
显示剩余2条评论

23

编辑:我要冒险说一句,你可能应该使用Dave提到的NSDataDetector。比正则表达式更不容易出错。


请看正则表达式。您可以使用NSRegularExpression类构建一个简单的正则表达式来提取URL,或者找一个可用的在线正则表达式。有关使用这个类的教程,请参见此处


您需要的代码基本上看起来像这样(使用John Gruber的超级URL正则表达式):

NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))" options:NSRegularExpressionCaseInsensitive error:NULL];
NSString *someString = @"This is a sample of a http://example.com/efg.php?EFAei687e3EsA sentence with a URL within it.";
NSString *match = [someString substringWithRange:[expression rangeOfFirstMatchInString:someString options:NSMatchingCompleted range:NSMakeRange(0, [someString length])]];
NSLog(@"%@", match); // Correctly prints 'http://example.com/efg.php?EFAei687e3EsA'

这将从任何字符串中提取第一个URL(当然,这没有错误检查,所以如果字符串确实不包含任何URL,则它将无法工作,但是请查看NSRegularExpression类以了解如何解决此问题。


5

使用方法如下:

NSError *error = nil;
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink
                                                           error:&error];

[detector enumerateMatchesInString:someString
                           options:0
                             range:NSMakeRange(0, someString.length)
                        usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop)
 {
     if (result.resultType == NSTextCheckingTypeLink)
     {
         NSString *str = [NSString stringWithFormat:@"%@",result.URL];
         NSLOG(%@,str);

     }
 }];

这将逐个输出someString中的所有链接。

3

使用这个:

NSURL *url;
NSArray *listItems = [someString componentsSeparatedByString:@" "];

for(int i=0;i<[listItems count];i++)
{
    NSString *str=[listItems objectAtIndex:i];
    if ([str rangeOfString:@"http://"].location == NSNotFound)
        NSLog(@"Not url");
    else 
        url=[NSURL URLWithString:str];
}

+1 感谢您的代码,它确实可以工作,而且以数组格式呈现,这对某些事情可能很有用 :-) 再次感谢。 - 0SX

3

Swift 2 :

let input = "This is a test with the URL https://www.hackingwithswift.com to be detected."
let detector = try! NSDataDetector(types: NSTextCheckingType.Link.rawValue)
let matches = detector.matchesInString(input, options: [], range: NSMakeRange(0, input.characters.count))

for match in matches {
    let url = (input as NSString).substringWithRange(match.range)
    print(url)
}

Source


这里有个错误的踩票,请有人点个赞吧!它是有效的! - Alex Hall

2
使用Swift 2.2 - NSDataDetector
let string = "here is the link www.google.com"
let types: NSTextCheckingType = [ .Link]
let detector = try? NSDataDetector(types: types.rawValue)
detector?.enumerateMatchesInString(string, options: [], range: NSMakeRange(0, (string as NSString).length)) { (result, flags, _) in
    if(result?.URL != nil){
        print(result?.URL)
    }
}

这个答案没有问题,我又测试了一遍,完美地运行了!!! - KarimIhab
1
我曾经给这个投了反对票,但后来发现我的源数据中有一个错误。这个程序是有效的,抱歉! - Alex Hall

2
有趣的是你提到了three20,那是我要去寻找答案的第一个地方。以下是来自three20的方法:
- (void)parseURLs:(NSString*)string {
    NSInteger index = 0;
    while (index < string.length) {
        NSRange searchRange = NSMakeRange(index, string.length - index);
        NSRange startRange = [string rangeOfString:@"http://" options:NSCaseInsensitiveSearch
                             range:searchRange];
        if (startRange.location == NSNotFound) {
            NSString* text = [string substringWithRange:searchRange];
            TTStyledTextNode* node = [[[TTStyledTextNode alloc] initWithText:text] autorelease];
            [self addNode:node];
            break;
        } else {
            NSRange beforeRange = NSMakeRange(searchRange.location, startRange.location - searchRange.location);
            if (beforeRange.length) {
                NSString* text = [string substringWithRange:beforeRange];
                TTStyledTextNode* node = [[[TTStyledTextNode alloc] initWithText:text] autorelease];
                [self addNode:node];
            }

            NSRange searchRange = NSMakeRange(startRange.location, string.length - startRange.location);
            NSRange endRange = [string rangeOfString:@" " options:NSCaseInsensitiveSearch
                             range:searchRange];
            if (endRange.location == NSNotFound) {
                NSString* URL = [string substringWithRange:searchRange];
                TTStyledLinkNode* node = [[[TTStyledLinkNode alloc] initWithText:URL] autorelease];
                node.URL = URL;
                [self addNode:node];
                break;
            } else {
                NSRange URLRange = NSMakeRange(startRange.location,
                                         endRange.location - startRange.location);
                NSString* URL = [string substringWithRange:URLRange];
                TTStyledLinkNode* node = [[[TTStyledLinkNode alloc] initWithText:URL] autorelease];
                node.URL = URL;
                [self addNode:node];
                index = endRange.location;
            }
        }
    }
}

每次在第一个if部分之后执行[self addNode:node];,都会添加一个找到的URL。这应该可以让你开始工作!希望这有所帮助。 :)

1
当然,如果编译iPhone应用程序,使用这种复杂的代码而不是NSRegularExpression类是没有必要的。 - Itai Ferber

2

@0SX,请确保使用正则表达式和Gruber的匹配模式来处理URL。这是获取URL子字符串的最佳方法。 - Cesar A. Rivas

0

Swift 4.x
Xcode 12.x

let string = "This is a test with the URL https://www.hackingwithswift.com to be detected. www.example.com"
let types: NSTextCheckingResult.CheckingType = [ .link]
let detector = try? NSDataDetector(types: types.rawValue)
detector?.enumerateMatches(in: string, options: [], range: NSMakeRange(0, (string as NSString).length)) { (result, flags, _) in
    if(result?.url != nil){
        print(result?.url)
    }
}

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