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サイト上でそのコンテンツのページに遷移する
勝手にアプリに遷移すると煩わしいのでは?と思うかもしれませんが、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に設定します。
ドメインに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 ID
とBundle 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"
HTTPSプロトコルでapple-app-site-associationファイルを供給する場合、MIME型がapplication/jsonであるプレーンテキストファイルであってもよく、その場合署名は必要ありません。
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 Links
やSearch API
のWeb 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 Banner
のapp-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見直したり、検証してみたいところです。
参考資料
- Seamless Linking to Your App - WWDC 2015 - Videos - Apple Developer
-
Breaking down iOS 9 Universal Links
- 分かりやすい!
-
HOKO - Universal Links
- ディープリンクサービスサイトの
Universal Links
解説記事
- ディープリンクサービスサイトの
- Promoting Apps with Smart App Banners