SwiftのNSDataDetectorクラスで文字列内にあるURLを検出する

## 概要

NSDataDetector クラスを利用して URL を検出し、その結果を考察する。

## 今回の動作確認環境

Xcode 8.3.3 + Swift 3.1 + Playground

## ソースコード

```swift
import UIKit

/**
 文字列内のURLを検出する
 - parameter str: 調査対象の文字列
 - returns: 検出したURL情報(NSTextCheckingResult)の配列
 */
func detectLinks(_ str: String) -> [NSTextCheckingResult] {
  let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
  if let d = detector {
    return d.matches(in: str, range: NSMakeRange(0, str.characters.count))
  } else {
    return []
  }
}

/**
 URL情報を出力する
 - parameter links: URL情報(NSTextCheckingResult)の配列
 */
func printLinks(_ links: [NSTextCheckingResult]) {
  links.forEach {
    // NSRange
    print("range.location: \($0.range.location)")
    print("range.length: \($0.range.length)")
    // URL?
    print("url: \(String(describing: $0.url))")
    print("")
  }
}

// テストパターン
let tests: [String] = [
  "hello, world", // URL無し
  "https://niwasawa.github.io/ hello", // 文字列の先頭にURL
  "hello https://niwasawa.github.io/", // 文字列の末尾にURL
  "hello https://niwasawa.github.io/ http://d.hatena.ne.jp/niwasawa/ goodbye", // 複数のURL
  "hello https://niwasawa.github.io/ https://niwasawa.github.io/ goodbye", // 同じURLが複数出現
  "hellohttps://niwasawa.github.io/ http://d.hatena.ne.jp/niwasawa/ goodbye", // URLの前に文字列
  "https://niwasawa.github.io/あ http://d.hatena.ne.jp/niwasawa/%E3%81%82", // 日本語を含むURL
  "http://a", // 不完全なURL
  "hoge://a", // hogeなURL
]

tests.forEach {
    let links = detectLinks($0)
    print("======================================================================")
    print("test: [\($0)]\n")
    printLinks(links)
}
```

## 出力結果

```
======================================================================
test: [hello, world]

======================================================================
test: [https://niwasawa.github.io/ hello]

range.location: 0
range.length: 27
url: Optional(https://niwasawa.github.io/)

======================================================================
test: [hello https://niwasawa.github.io/]

range.location: 6
range.length: 27
url: Optional(https://niwasawa.github.io/)

======================================================================
test: [hello https://niwasawa.github.io/ http://d.hatena.ne.jp/niwasawa/ goodbye]

range.location: 6
range.length: 27
url: Optional(https://niwasawa.github.io/)

range.location: 34
range.length: 31
url: Optional(http://d.hatena.ne.jp/niwasawa/)

======================================================================
test: [hello https://niwasawa.github.io/ https://niwasawa.github.io/ goodbye]

range.location: 6
range.length: 27
url: Optional(https://niwasawa.github.io/)

range.location: 34
range.length: 27
url: Optional(https://niwasawa.github.io/)

======================================================================
test: [hellohttps://niwasawa.github.io/ http://d.hatena.ne.jp/niwasawa/ goodbye]

range.location: 0
range.length: 32
url: Optional(hellohttps://niwasawa.github.io/)

range.location: 33
range.length: 31
url: Optional(http://d.hatena.ne.jp/niwasawa/)

======================================================================
test: [https://niwasawa.github.io/あ http://d.hatena.ne.jp/niwasawa/%E3%81%82]

range.location: 0
range.length: 28
url: Optional(https://niwasawa.github.io/%E3%81%82)

range.location: 29
range.length: 40
url: Optional(http://d.hatena.ne.jp/niwasawa/%E3%81%82)

======================================================================
test: [http://a]

range.location: 0
range.length: 8
url: Optional(http://a)

======================================================================
test: [hoge://a]

range.location: 0
range.length: 8
url: Optional(hoge://a)
```

## 考察

- 不完全なURLも検出される
- URLに日本語が混じっていてもパーセントエンコーディングされた結果が検出される
- 文字列内に同じURLが存在していてもそれぞれ検出される
- 「hellohttps://a」や「hoge://a」のようなURLも検出される
- NSDataDetector で検出した結果について、要件に合うようにフィルタリングするのが良さそう

## 参考資料


- [NSDataDetector \- Foundation \| Apple Developer Documentation](https://developer.apple.com/documentation/foundation/nsdatadetector)
- [NSTextCheckingResult \- Foundation \| Apple Developer Documentation](https://developer.apple.com/documentation/foundation/nstextcheckingresult)
- [Swift 3でStringからURLの配列を抽出する\(Extract an array of URLs from a String in Swift 3\) \- Qiita](http://qiita.com/hsylife/items/5b4b5f35c7be5588726f)