UIWebView / WKWebView / SFSafariViewController から、自分自身を起動する Universal Links を踏んだ場合の違和感あるUXへの対処

ios

1 はじめに

Universal Linksを使用すると、https://〜のリンクからインストールされているアプリを起動することが可能です。
Universal Linksを試してみました。関連づけファイル(apple-app-site-association)はS3に置きました。

これは、Webベージや、メールで送ったリンクからのアプリ誘導には非常に有効な手段です。

しかし、起動されるべきアプリが既に実行中であり、そのアプリの中で使用しているUIWebView等で、自分自身を起動するUniversal Linksを踏んだ場合どうなるのでしょう?

っと言うことで、調べみました。

2 試験環境

(1) 試験用ページ

試験のために次の2つのページを用意しました。

  • アクセス元のページ
  • アクセス先のページ

「アクセス元のページ」には、「アクセス先のページ」(Universal Links用のapple-app-site-associationが設置してある)へのリンクが置かれています。

001

こちらは、「アクセス先のページ」です。上のページでリンクを選択すると、ここに遷移します。

002

(2) テストアプリ

テストのために、Universal Linksで起動するテストアプリを作成しましました。

トップには、「UIWebView」「WKWebView」「SFSafariViewController」の3つのボタンがあり、それぞれのビューを開いて、先の「アクセス元のページ」を開くようになっています。

016

また、Universal Linksで起動された場合は、下記のように、ログ表示するとともに、別のビューを開いてURLを表示するようにしました。

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
        print("application(_:continue:restorationandler:)")

        let url = (userActivity.webpageURL?.absoluteString)!
        print("URL=\(url)")
        viewController.openUniversalLinksView(url: url)
    }
    return true
}

下図は、テストアプリを実行している様子です。 最初に、ボタンをタップして、各Webビューを表示しています。そして、一旦アプリを終了させ、SafariでUniversal Linksを選択することでアプリを起動しています。

018

3 動作確認

(1) UIWebView

UIWebViewからUniversalLinksのリンクをタップすると「アクセス先のページ」が表示されてしまいます。(目的のビューは表示されない)

アプリは既に起動しているので、「Universal Linksは、問題ない」とも言えますが、これは、意図した動作ではないかも知れません。

005 006 007

(2) WKWebView

WKWebViewでも同じく「アクセス先のページ」が表示されてしまいます。(目的のビューは表示されない)

008 009 010

(3) SFSafariViewController

SFSafariViewControllerの場合は、リンクをタップしても、表示は変わりませんでした。(目的のビューは表示されない) しかし、application(_:continue:restorationHandler:)を通過したログが表示されていました。

SFSafariViewControllerは、テストアプリとは、別のプロセスとして動作しているため、バックグランドになっているアプリがUniversal Linksでキックされたいう理解で良いのでしょうか?

しかし、「完了」でSFSafariViewControllerを閉じても、目的のビューには遷移していませんでした。

011 012 013

これは、表示されていたログです。

application(_:continue:restorationHandler:)
URL=https://universal-links-bucket.s3.amazonaws.com/index.html

4 対応要領の一例

動作確認の結果、どのWebViewも、予定した動作ができていないと言えそうです。(目的のビューは表示されない) アプリの仕様によって、対策は色々あると思いますが、今回は、その一例を考えてみました。

(1) UIWebViewの対応

UIWebViewでは、UIWebViewDelegateプロトコルを実装することでwebView(_:shouldStartLoadWith:navigationType:)で遷移前に処理をトラップすることが可能です。

ここで遷移先を確認して、対象URLだった場合は、UIWebViewを表示しているビューをクローズして、目的のビューを表示しました。

func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
    let url = request.url?.absoluteString
    if url == DestinationUrl {
        // UniversalLinksで起動された場合の処理
        let viewController = navigationController?.viewControllers.first as! ViewController
        viewController.openUniversalLinksView(url: url!)
        // NavigationControllerで戻る
        _ = navigationController?.popViewController(animated: true)
        // 遷移をキャンセル
        return false
    }
    return true
}

(2) WKWebViewの対応

WKWebViewの場合も、先の対応と同じです。webView(_:decidePolicyFor:decisionHandler:)でトラップして、同じ要領で目的のビューに遷移しています。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

    let url = navigationAction.request.url?.absoluteString
    if url == DestinationUrl {
        // UniversalLinksで起動された場合の処理
        let viewController = navigationController?.viewControllers.first as! ViewController
        viewController.openUniversalLinksView(url: url!)
        // NavigationControllerで戻る
        _ = navigationController?.popViewController(animated: true)
        // 遷移をキャンセル
        decisionHandler(.cancel)
        return
    }
    decisionHandler(.allow)
}

(3) SFSafariViewControllerの対応

SFSafariViewControllerでは、遷移先に移動する前にトラップする処理を書けませんので、application(_:continue:restorationHandler:)側から、対処を考えてみました。

Universal Linksで起動されて、目的のビューに遷移する際に、もしSFSafariViewControllerが表示されていたらクロースする処理を追加しました。

func openUniversalLinksView (url: String){
    // もし、SFSafariViewControllerが表示されていたらクローズする
    sfview?.dismiss(animated: true, completion: nil)

    self.url = url
    performSegue(withIdentifier: "toUniversalLinks",sender: nil)
}

(4) 動作確認

対応したテストアプリを実行している様子です。 SFSafariViewControllerから遷移する場合に、一旦トップのビューが表示されてしまうのは、SFSafariViewControllerを閉じてしまわないと、画面遷移ができなかったためです。適切な方法が有りましたら、是非教えてやって下さい。

019

5 最後に

今回は、Universal Linksを自らが踏んだ場合の対策を検討してみました。

ここで紹介した対策は一例に過ぎません。例えば、今回のテストアプリでは、UINavigationControllerでWeb表示のビューに遷移しているので、popViewController(animated:)を使用しましたが、Modalで遷移している場合などは、dismiss(animated:completion:)で、ビューを閉じる必要があるかも知れません。

どちらにしても、UXに違和感を与えないためには、何らかの処置が必要だと思いました。

参考のため、テストアプリ(対策は終わっています)を置きました。 再利用する場合は、Universal Linksのドメイン及びapple-app-site-associationについては、別途、準備が必要です。
github [GitHub] https://github.com/furuya02/ULSample

6 参考リンク


Apple Refelence Support Universal Links
Apple プログラミングリファレンス ユニバーサルリンクへの対応
Universal Linksを試してみました。関連づけファイル(apple-app-site-association)はS3に置きました。
[iOS] ディープリンク(Custom URL Scheme)でアプリを起動する