Universal Linksとは
urlからアプリをシームレスに起動できる機能のことです。
ディープリンクとの大きな違いは確認のダイアログなしでアプリに遷移できることです。
UX上の利点や特徴などは以下の記事で詳しく解説されています。
モチベーション
今回Universal links対応をiOS側の担当者として調査から実装までやりました。
しかしこれが曲者で、
- OSごとに対応方法が異なっていたり、
- 事前に対策を打たないと特定の環境で動作確認できなかったり
- 挙動にOS差分があって中々原因に気づけなかったはまりどころがあったり、
などなど結構ハマってしまいました。。。
そこで、同じようにiOS14からUniversal Linsに対応する方が苦しまなくてよいようにするため、以下の情報を記事にしました。
- iOS14時点のUniversal Links対応手順
- Universal Linksの実装に役立つテクニック
iOS14からのUniversal Links対応手順
1.apple-app-site-association
の作成
apple-app-site-association
とは、iOSアプリへの遷移の資格情報を記載するファイルです。
注意点
- .jsonなどのファイル形式を指定しないこと。(ただの
apple-app-site-association
として作成する。) - iOS13以前と以降で書き方が異なるが、13以前のOSでUniversal Linksを使用したい場合は旧形式で記載すること。
- iOS13以前の書き方→apple-app-site-associationのpaths(遷移対象パス)の作り方(UniversalLinks対応)
- iOS13以前のOSを気にする必要がない場合は、後述する記載形式で作成することをお勧めします。パラメータを柔軟に指定できるためです。
- iOS13以前の書き方→apple-app-site-associationのpaths(遷移対象パス)の作り方(UniversalLinks対応)
記載方法
それでは中身を見ていきます。ざっくり以下の要素を記載する必要があります。
key | valueに記載するもの | 例 |
---|---|---|
appIDs | [teamID] + "." + [BundleID]を配列で記載 | ABCDE12345.com.example.app |
components | 遷移を許可するパスの情報を記載 | 後述 |
webcredentials | [teamID] + "." + [BundleID]を配列で記載 | ABCDE12345.com.example.app |
{
"applinks": {
"details": [
{
"appIDs": [ "ABCDE12345.com.example.app", "ABCDE12345.com.example.app2" ],
"components": [
{
"#": "no_universal_links",
"exclude": true,
"comment": "Matches any URL whose fragment equals no_universal_links and instructs the system not to open it as a universal link"
},
...
},
"webcredentials": {
"apps": [ "ABCDE12345.com.example.app" ]
},
...
}
teamIDとbundleIDがBuildConfigurationごとに異なっている場合は、それら別に記載する必要があるので注意です。
僕はRelaseとDebugのteamIDを変える対応が漏れていて爆死しました。
apple-app-site-association
の記載ミスはファイルを修正してまた配置し直す作業が発生するため、修正時のリードタイムが大きくなります。
入念にレビューしてもらってから配置しましょう。
components内の項目
key | valueに記載するもの | 例 |
---|---|---|
/ | 遷移を許可するパス | /foo |
# |
URLのフラグメント | no_universal_links |
? | パラメータ | { "articleNumber": "????" } |
comment | コメント(遷移には影響しない) | "fooへ遷移を許可します。" |
"components": [
{
"#": "no_universal_links",
"exclude": true,
"comment": "Matches any URL whose fragment equals no_universal_links and instructs the system not to open it as a universal link"
},
...
]
覚えておきたいのは以下のポイントです。
- "/"で記載する遷移を許可するパスはドメイン以降のパスを指定する。
- 例えば
https://example.com/foo
の場合は/foo
のみ記載する。
- 例えば
- 全体で正規表現が使用できる。
-
?
はランダムな1文字を許可 -
*
はランダムな文字列を許可
-
パラメータの詳しい指定方法は2019年のWWDCの動画が参考になります。
2.apple-app-site-association
をサーバーに配置する。
1で作成したapple-app-site-association
をUniversalLinksを起動させるURLのドメインのサイトに配置します。
配置するパスは以下のように.wellknown
配下を指定する必要があります。
https://<fully qualified domain>/.well-known/apple-app-site-association
こうして配置されたapple-app-site-association
はアプリのインストール時にアプリ側から取得され、UniversalLinksに遷移の可否判定に使用されます。
iOS14以降のapple-app-site-association
取得上の注意
iOS13までは.well-knownのパスにアプリ方直接apple-app-site-association
を取得しに行っていました。
しかしiOS14から取得経路が変わり、AppleのCDNを経由してapple-app-site-association
が取得されるようになりました。
図にすると以下にようになります。
この変更による問題点は、AppleのCDNがapple-app-site-association
を取得できない場合、UniversalLinksが機能しない ということです。
例えばStaging環境でIP制限を設けている場合だと、AppleのCDNからのアクセスを弾いてしまい、アプリがCDNからapple-app-site-association
を取得できず、UniversalLinksが機能しなくなってしまう事態が発生します。
これは後述するAssociated Domainsの設定でドメインにmodeを指定することで回避できます。
mode=developer
を指定することで開発者の端末であればCDNを経由せずに直接ドメインの.well-knownにJSONを取得しに行くことができます。
Staging環境でもUniversalLinksのテストをしたい場合は、上記の設定によってテストが可能です。
※ ドキュメントにはmode=developerを指定する場合、「使用するデバイスでオプトイン」する必要があると記載されています。
なんのことかというと設計アプリのデベロッパ画面の以下の項目です。
(Appleのドキュメントはこういう細かいとこの記載が分かりにくいと思う。。。)
iOS14でのUniversalLinksの変更点については以下を参考にさせていただきました。
公式によるiOS14でのUniversalLinksの変更点の説明は2020年のWWDCの動画で確認できます。
3.Associated Domains
のCapabilitiesを追加
CapabilitesにAssociated Domains
を追加します。
ここには2でサーバーに配置したapple-app-site-association
を取得しにいくドメインを記載します。
以下の形式で追加します。
<service>:<fully qualified domain>
2で記載したCDNを経由せずにドメインにapple-app-site-association
を取得しに行く設定にするためには、ここでmodeを指定します。
Releaseビルドではmode=developer付きのstagingドメインを記載する必要はないので、BuildConfigurationごとにentitlementsを分ける対応になりそうです。
ドメイン記載時の注意
apple-app-site-association
を配置したドメインとAssociatedDomainsの記載は一致している必要があります。
例えばhttps://www.example.com/.well-known/
にapple-app-site-association
を配置したとします。
この時**www
なしのapplinks:example.com
を記載していた場合、iOS14ではUniversalLinksが機能しません**。
iOS13まではexample.com
のようなルートのドメインのみapplinksに指定すれば、apple-app-site-association
の配置先がwww.example.com
のようなサブドメインでも取得されました。
しかしiOS14ではapple-app-site-association
の配置先のドメインと一致しているAssocaitedDomainsがないとUniversalLinksは機能しなくなっていました。
ドキュメントの通り、ルートのドメインと遷移を許可したいサブドメインを指定していれば基本発生しない事象ですが、仮にiOS13まではルートのドメインのみAssociatedDomainsに指定してサブドメインからapple-app-site-association
を取得していた場合、iOS14以降は機能しなくなる恐れがあリますので注意が必要です。
4.UniversalLinks起動時のハンドリングを実装
UniversalLinksの起動を許可するパスのURLを開いた時、AppDelegateの以下のDelegateメソッドが呼ばれます。
func application(
_ application: NSApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([NSUserActivityRestoring]) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, // ブラウザからの遷移か確認
let incomingURL = userActivity.webpageURL, // URLを取得
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
return false
}
return true
}
ここでtrueを返せばアプリが開きます。
受け取ったパスが遷移を許可したくないパスかどうか確認するにはuserActivityからURLを取得してそのURLを確認します。
URLにしてしまえばあとはComponentからパスを取得するなりパラメータを取得するなりなんなりできます。
ここまで対応すればUniversalLinksによるアプリの起動が可能になっていると思います。
UniversalLinksの実装に役に立ったテクニック
SimulatorでUniversalLinkの動作を確認する。
アプリをsimulatorにインストールした状態で以下のコマンドを実行します。
ブラウザで直接URLを書き込むことで確認もできますが、コマンド一つでPC上から確認できるので、こちらの方が確認が簡単でした。
xcrun simctl openurl booted {URL}
AppleのCDNから取得できるapple-app-site-associationを確認する。
これは以下のエンドポイントに対してGETのリクエストを送ることで確認できます。
GET https://app-site-association.cdn-apple.com/a/v1/{確認したいdomain}
こちらを参考にさせていただき、実際にapple-app-site-associationが取得できることを確認しました。
※ヘッダーにHOST=app-site-association.cdn-apple.com
の指定が必要とのことですが、なしでも取得できました。
CharlesやProxyManでapple-app-site-associationの取得をキャプチャする際の注意
AppleのCDNに対するリクエストはCharlesなどの通信デバッグツールを噛ませるとapple-app-site-associationの取得に失敗します。
simulatorやデバイスに対して、CharlesのRoot証明書を信頼させていてもこれは不可能でした。。。
僕はAppleのCDN経由からapple-app-site-associationを確認したく、何度もCharlesを噛ませながらリクエストを送っていたんですが、この時間は徒労に終わってしまいました。
(もし同様に通信デバッグツールを使ってもCDNからのapple-app-site-associationをキャプチャできた方がいましたら環境や設定など教えていただけると嬉しいです。)
まとめ
Universal Links対応は開発の作業自体は少ないものの、サーバーチームとの連携、概念の理解、OSごとの対応手順差分・挙動差分などなど、ハマるポイントが結構あると思いました。
今後UniversalLinksに対応する方が僕と同じようなポイントでハマってしまわないようにこの記事がお役に立てば幸いです。