URLスキーム・独自ディープリンク実装に代わる、Universal Links(iOS 9で導入)でより良いUXを実現

  • 833
    いいね
  • 6
    コメント
この記事は最終更新日から1年以上が経過しています。

iOS 9で、Universal Linksという仕組みが導入されます。

WWDC資料:
Seamless Linking to Your App - WWDC 2015 - Videos - Apple Developer

これまでのURLスキーム・独自ディープリンク実装と機能的に似ていますが、それがAPIとして公式にサポートされ、より良いUXが提供出来るようになる模様です。

Search API(特にWeb Markup API)と同時にiOS 9対応に向けてWeb側で是非対応検討しておいた方が良い事項だと思っています:
searchapi - iOS 9の「Search API Best Practices and FAQs」が公開されたので読み解いてみた - Qiita

Universal Linksとは?

ディープリンク系の技術としてAppleから正式に提供されたもの、という理解で良さそうです。

通常のHTTPのリンク経由でアプリを起動出来る

Web・iOSアプリともに対応している状態で、Webの特定コンテンツのURLにアクセスすると、こういう挙動となります。

  • アプリがインストール済み: 直接アプリに飛んでコンテンツが表示される
  • アプリが未インストール: 通常通りWebページが開く

iOS 8までのカスタムURLスキーム(player-ookami://some-contentなど)のようなアプリ起動専用のURLが不要で、そのコンテンツのWeb URL1つに統一されます。

例えば、http://some-site/some-content はある特定コンテンツを一意に指すもので、Webサイトが開かれるかもしれないしアプリが開かれるかもしれない、みたいなイメージです。
まさにUniversal Linksという名前通りですね。

UX上の大きな違い

これまでのカスタムURLスキームの仕組みを使ったディープリンク実装では、一旦コンテンツのWebページに飛んでからアプリに自動遷移 or 「アプリで開く」ボタンなどで開くという挙動でした。
(自動遷移は、せっかく読もうとしたところで画面が慌ただしく動く感じで好きじゃ無い。お行儀良くないと思っている。)

一方、Universal Linksの場合、Webサイトのコンテンツにアクセスしようとすると即座にアプリにシームレスに遷移し、その間にWebページなどが挟まれません。

例えば、WWDCサイトを見ていてUniversal Links対応のサイトのリンクをタップすると、以下のような挙動になります。

  • もしWWDCアプリがインストールされていたら即座にアプリに移り、そのコンテンツに対応する画面が表示される
  • アプリがインストールされていなければ、普通にWebサイト上でそのコンテンツのページに遷移する

Artboard 1.png

勝手にアプリに遷移すると煩わしいのでは?と思うかもしれませんが、iOS 9ではアプリ間の遷移がPUSH遷移ぽくなっていて、またステータスバー左に戻るボタンが付く、という対応とセットなので問題無いと思っています。
「Webで直接開いた方が良いのに」と思われないように、スムーズにコンテンツを表示することがアプリには求められますが( ´・‿・`)

カスタムURLスキームと違って一意性が担保できる

カスタムURLスキームは、アプリ同士で重複することがシステム上許されてしまっていて、また被った時にどちらが呼び出されるか不定となっています。(後勝ち説などありますが、仕様的には不定)

例えば、今開発中のPlayer!は、本当はplayer://にしたいところですが、衝突を避けてplayer-ookami://にしています。これも衝突を避けやすくはなりますが、確率の問題で、これでも衝突し得ます。

例えば、LINEのURLスキームはline://ですが、これをカスタムURLスキームとして宣言したアプリをリリースしてLINEヘの遷移を奪うことも出来ちゃいます(有名なのでもしかしたらこれは例外的にレビューで弾かれるかもしれないけど)。

一方、Universal Linksはhttpリンクがキーとなっていて、それはドメインで一意性が担保されるので重複が起こりえなくなります。

Universal Links対応手順

さっくり以下の対応が必要となります。

  • Webサイト
    • 独自ドメインに配置
    • SSL対応
    • 署名したJSONファイル配置
  • iOSアプリ
    • iOS 9以上
    • Xcode 7・iOS 9 SDKでビルド
    • Capabilities設定
    • AppDelegateでハンドリング

1. iOSアプリでCapabilitiesの設定

Capabilitiesの設定で、`Associated Domains’のDomainsに設定します。

Screen Shot 2015-07-06 at 5.37.01 AM.png

ドメインにapplinks:の接頭辞を付けたものを記述していきます。

例:
https://www.facebook.com/ -> applinks:facebook.com

サブドメイン、wwwの有無などの揺れも漏れなく記載する必要があるので注意です。

ここに記述したサイトのapple-app-site-associationファイル(次の手順で説明)が、アプリ初回起動時にダウンロードされるようです。

2. apple-app-site-associationというファイルをWebサイトに配置

Webサイトは、SSL対応してある必要があります。
Qiitaはこのままじゃダメっぽいですね( ´・‿・`)

まず以下のようなJSONファイルを用意します。

{
  "applinks": {
    "apps": [],
    "details": {
      "TBEJCS6FFP.com.domain.App": {
        "paths":[ "*" ]
      }
    }
  }
}

pathsには、対応しているパスを記述しましょう。
全指定は、"*"です。
部分指定したい場合は、["/wwdc/news/", /videos/wwdc/2015/*]のように指定しましょう。

TBEJCS6FFP.com.domain.App部分は、Team IDBundle Identifierを連結したものです。

appsには何を指定すれば良いのか謎です( ´・‿・`)

署名して配置

JSONファイルをSSL証明書で署名する必要があります。

これで、署名されたapple-app-site-associationが生成されるのでWebサイトのルートに配置しましょう。

openssl smime \
    -sign \
    -nodetach \
    -in "unsigned.json" \
    -out "apple-app-site-association" \
    -outform DER \
    -inkey "private-key.pem" \
    -signer "certificate.pem"

3. iOSアプリでのハンドリング

こんな感じです。

import UIKit

extension AppDelegate {
    func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
        // typeをチェック
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
            let webpageURL = userActivity.webpageURL!
            if !handleUniversalLink(URL: webpageURL) {
                // コンテンツをアプリで開けない場合は、Safariに戻す
                UIApplication.sharedApplication().openURL(webpageURL)
                return false
            }
            // 多分処理後にここかどこか適切な場所で呼ぶ必要あり
            restorationHandler()
        }
        return true
    }

    private func handleUniversalLink(URL url: NSURL) -> Bool {
        if let components = NSURLComponents(URL: url, resolvingAgainstBaseURL: true), let host = components.host, let pathComponents = components.path?.pathComponents {
            switch host {
            case "my-domain.com":
                // ここでハンドリング
                return true
            default:
                return false
            }

        }
        return false
    }
}
  • func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> BoolはHandoff・Search APIでも使われるメソッドなのでtypeチェック必要
  • URLパースは、NSURLComponentsを使うと便利
  • どうしても開けない例外系として、その場合はSafariに飛ぶようにすることが推奨
    • 無限ループにならないか気になるけど、多分遷移元見てどうにかやってくれるはず

というわけで、手順は以上です。
Web側で現状要件満たしていない場合は追加対応がちょっと面倒そうですが、それ以外はわりとすんなり組めそうですね。

「アプリで開く」ボタンに相当するものは?

よく「アプリで開く」ボタンのようなものが、Webのコンテンツページに配置されていますが、Universal Linksはそのあたりの面倒は見てくれません。

このあたりは基本的にこれまでと変わらず、独自URLスキーム・ディープリンク実装か、Smart App Bannerを使う必要があります。

AppleはSmart App Banner推しの模様です。

Smart App Bannerのcontentのapp-argument指定が大事になってくるかも

今回このUniversal LinksSearch APIWeb Markupなどが出てきたことと合わせてかapp-argumentに、サイトのURLを渡すとそのURLがインデックスされるとのことです。

例:

<head>
<meta name="apple-itunes-app" 
      content="app-id=640199958, app-argument=https://developer.apple.com/wwdc/schedule, affiliate-data=optionalAffiliateData">
</head>

Universal Links使っていれば、そのURLのパースとSmart App Bannerapp-argumentのURLパースが全く同じロジックで出来て良いですね。

Search APIについてはこちら:
searchapi - iOS 9の「Search API Best Practices and FAQs」が公開されたので読み解いてみた - Qiita

問題点

iOS 9でしか使えない

これが一番悩ましいですね。
iOS 8以下にも対応する場合は、URLスキーム・独自ディープリンクと両対応することになるかと思います。

iOS9でカスタムURLスキームの遷移に失敗するときの注意点 - Qiitaと合わせて、ちょっと色々考えることがありそうです。

Webサイトに要件がある

Universal Links対応手順に書いた通りですが、現時点で要件満たさない場合は、対応作業が必要となります。

新しい種類のページを作ってその時点でリリースしているiOSアプリが開けないパターンがあり得そう

非対応ページはapple-app-site-association で除外すれば良いですが、その最新設定ファイルをiOSアプリがまだ取得出来ていない場合、Webでそのページにアクセスされると意図せずアプリに遷移してしまい、ハンドリング出来ないことがありそうです(実装例の通りSafariに飛ばすことになる)。
設定ファイルの更新タイミングは初回起動時とBreaking down iOS 9 Universal Linksには書いてありますが、インストール直後の1回だけなのか起動する度になのか更新タイミングが気になります。
WWDC見直したり、検証してみたいところです。

参考資料