【iOS】UITextViewに埋め込めるリンク3種類の紹介

以下の3種類のリンクを紹介。
[1] ブラウザでインターネット上のURLを開く
[2] アプリ内の特定のページを開く（好きなコードを動かせるので、実際はなんでもできる）
[3] 外部アプリを開く

**例えばこういう時に使えます！**
[1][2] 規約やポリシーへの同意を求める文面で、青い部分をタップした時にウェブやアプリ内の該当ページを見せたい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272677/3a028b12-de9f-6572-8f0c-48bd65cb6a40.png)
[3-A] カメラ等の利用許可を得るために、設定アプリを開かせたい。
[3-B] 住所・施設等の紹介ページに、マップアプリを開くリンクをつけたい。

# 1. インターネット上のURLへのリンク
基本です。
AttributedStringで文章を作り、rangeを指定して`.link`Attributeを付与。
その後、textViewのattributedTextに設定。

```swift:ViewController
class LinkUIViewController: UIViewController {
    @IBOutlet weak var textView: UITextView! {
        didSet {
            let baseString = "Googleへのリンク"
            let paragraph = NSMutableParagraphStyle()
            let attributedString = NSMutableAttributedString(string: baseString, attributes: [NSAttributedString.Key.paragraphStyle: paragraph])
            attributedString.addAttribute(.link, value: "https://www.google.com/", range: NSString(string: baseString).range(of: "Google"))
            textView.attributedText = attributedString
        }
    }
}
```

# 2. アプリ内リンク
## アプリ内の遷移先をStoryboardで作る
まず、遷移先の画面を用意する。既にある場合は飛ばしてOKです。

今回の遷移先のViewControllerをもつStoryboardを作成。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272677/2e6d6504-bbe9-d3f1-9019-4d00575ce7d5.png)
利用規約のViewControllerを作る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272677/91680873-78ff-0851-238d-2e0c46c64b7f.png)
それぞれのViewControllerにIDを振る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272677/e3342df7-8c29-7bd8-b9da-58c5cb1f9629.png)
なお、Storyboard IDとRestoration IDは同じにすることが推奨されており、Restoration ID設定の`Use Storyboard ID`にチェックを入れるとStoryboard IDと同一名扱いになる。

## コード
1と同様にURLを埋め込むが、これを任意の文字列にしておく。
今回は"TermOfUseLink"に。

```swift:LinkUIViewController.swift
class LinkUIViewController: UIViewController {
    @IBOutlet weak var textView: UITextView! {
        didSet {
            let baseString = "利用規約へのリンク"
            let paragraph = NSMutableParagraphStyle()
            let attributedString = NSMutableAttributedString(string: baseString, attributes: [NSAttributedString.Key.paragraphStyle: paragraph])
            attributedString.addAttribute(.link, value: "TermOfUseLink", range: NSString(string: baseString).range(of: "利用規約"))
            textView.attributedText = attributedString
        }
    }
}
```
そしてUITextViewDelegateの[`textView(_:shouldInteractWith:in:interaction:)`](https://developer.apple.com/documentation/uikit/uitextviewdelegate/1649337-textview)を使うと、URLを開く前に判定できる。
URLに先程の"TermOfUseLink"が設定されていた場合、Storyboard経由でViewControllerを呼ぶようにする。

```swift:UITextViewDelegateExtension.swift
import UIKit

extension LinkUIViewController: UITextViewDelegate {
    public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        let urlString = URL.absoluteString
        if urlString == "TermOfUseLink" {
            // Storyboardを呼び出し
            let storyboard = UIStoryboard(name: "MyPage", bundle: nil)
            // Storyboard内のViewControllerをIDから呼び出し
            let viewController = storyboard.instantiateViewController(withIdentifier: "TermOfUse")
            // 画面遷移
            navigationController?.pushViewController(viewController, animated: true)
            return false // 通常のURL遷移を行わない
        }
        return true // 通常のURL遷移を行う
    }
}
```
そしてここで`return false`すると通常のURL遷移はキャンセルされる。
単にURLを開く許可・不許可を判定する用途にも使える。

>Return Value
true if interaction with the URL should be allowed; false if interaction should not be allowed.

# 3.外部アプリへのリンク
## 設定を開く
定数として[`UIApplication.openSettingsURLString`](https://developer.apple.com/documentation/uikit/uiapplication/1623042-opensettingsurlstring)が利用でき、これを使うと標準で自分のアプリに関しての設定画面が開く。

```swift:ViewController
class LinkUIViewController: UIViewController {
    @IBOutlet weak var textView: UITextView! {
        didSet {
            let baseString = "設定へのリンク"
            let paragraph = NSMutableParagraphStyle()
            let attributedString = NSMutableAttributedString(string: baseString, attributes: [NSAttributedString.Key.paragraphStyle: paragraph])
                                                         // ↓これ
            attributedString.addAttribute(.link, value: UIApplication.openSettingsURLString, range: NSString(string: baseString).range(of: "設定"))
            textView.attributedText = attributedString
        }
    }
}
```

## 特定のアプリを開く
有名サービスの多くはiOS9以降のUniversal Links機能に対応しているので、Twitter等を呼ぶ際に特に意識することはないと思われる。
例えばGoogleMapなら、以下のようなURLで特定座標をMapアプリもしくはブラウザで開いてくれる。
`"https://www.google.com/maps/@42.585444,13.007813,6z"`
https://developers.google.com/maps/documentation/urls/ios-urlscheme

# まとめ
「3種類」と言っても、流れはどれも同じでした。
step1. テキストに`NSMutableAttributedString`でリンクを埋め込む
step2. `shouldInteractWith`でリンクを判定
step3. 必要に応じて好きな動作

これに限らずUITextViewDelegateはすごく強いので、うまく使ってやりたいところです。