TTTAttributedLabel で Peek & Pop に対応する (2016年5月更新)

TTTAttributedLabel は UILabel の代わりに利用できる, リンクの検出などの機能を備えたライブラリです. この記事では TTTAttributedLabel で表示しているリンクを iPhone 6s / 6s Plus で利用できる 3D Touch の機能の一つである Peek & Pop に対応させる方法を紹介します.

目標

この記事ではある ViewController の subview として TTTAttributedLabel を持ち, この TTTAttributedLabel の中に表示されているリンクを Peek & Pop に対応させる方法を紹介します.

動作のイメージ (Tweetbot 4 より):

Tweetbot 4 での Peek & Pop の動作例

(※Tweetbot 4 が TTTAttributedLabel を利用しているかどうかは分かりません)

TTTAttributedLabel を subview として配置させる方法や, TTTAttributedLabel でリンクを検出して表示する方法などはこの記事の対象外です.

TTTAttributedLabel

現在リリースされている1.13.4 は Peek & Pop に対応するために便利なメソッド linkAtPoint が実装されていません. c9d9219 で実装されているので, これ以降のバージョンを Git で取得して利用してください.

2016/5/14 更新: 2016/5 にリリースされた 2.0.0-[TTTAttributedLabel linkAtPoint:] が公開されました. 2.0.0 以降のバージョンを用いることで, 以下の手順に従って 3D Touch 対応を行えます.

viewDidLoad

Peek & Pop に対応するには registerForPreviewingWithDelegate:sourceView: を用いて, Peek & Pop を利用するビューを登録しておきます. ここでは UIViewController の view を登録します.

override func viewDidLoad() {
    // 省略

    if traitCollection.forceTouchCapability == .Available {
        registerForPreviewingWithDelegate(self, sourceView: view)
    }
}

UIViewControllerPreviewingDelegate

Peek & Pop に対応するには ViewController を UIViewControllerPreviewingDelegate に適合させます. サンプルコードは次のようになります.

extension ViewController: UIViewControllerPreviewingDelegate {
    func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
        let point = previewingContext.sourceView.convertPoint(location, toView: label)
        guard label.containslinkAtPoint(point) else { return nil }
        guard let link = label.linkAtPoint(point),
            url = link.result.URL else { return nil }
        // previewingContext.sourceRect をよしなに設定する
        let safariViewController = SFSafariViewController(URL: url)
        return safariViewController
    }

    func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
        presentViewController(viewControllerToCommit, animated: true, completion: nil)
    }
}

2行目の previewingContext:viewControllerForLocation: は Peek する際に呼び出されるメソッドです. location の場所にあるビューが Peek に対応しているのであれば, Peek する ViewController を返し, Peek に対応していないのであれば nil を返します. このメソッドには次のような処理が実装されています (label を TTTAttributedLabel とします):

  • 3行目で選択された座標を label 上の座標に変換します
  • 4行目で選択された座標がリンクの上かどうかを判定します
  • 5行目で選択された座標に対応するリンク (TTTAttributedLabelLink) を取得します
  • 6行目でリンクから URL を取得します
  • 8,9行目で URL を SFSafariViewController で開きます

previewingContext:viewControllerForLocation: では previewingContext.sourceRect に選択されているビューの CGRect を指定することで, 指定した CGRect の範囲以外にブラーがかけられます. しかし, リンクの形は必ずしも長方形とは限らないため, ラベル全体を sourceRect に設定するなど適当な対応が必要になります.

12-14行目の previewingContext:viewControllerForLocation: は Pop する際に呼ばれる処理です. 通常は Peek 時の ViewController をそのまま表示するので, viewControllerToCommit へ遷移する処理を記述します.

参考資料