目次
Session概要
ユニバーサルリンク(iOS9以降で使用可能)を使用すると、ユーザがアプリ内のコンテンツやWebサイトにスマートに移動できるようにすることができます。このセッションでは、ユニバーサルリンクの最新の改良により、デバイスにアプリがインストールされていない場合でも、モバイルとデスクトップの間でユーザが高度に統合された体験を味わえるようにする方法について https://developer.apple.com/videos/play/wwdc2019/717/
ユニバーサルリンクの概要
- ユニバーサルリンクとは、HTTPやHTTPSのURLでWebやアプリのリソースを示すものとしてAppleのOS(iOS, macOSなど)が認識する
- アプリのインストール状況に関わらず、1つのURLでコンテンツを示すことができる
- iOSはiOS 9以降、macOSは macOS 10.15以降で利用可能
- アプリとWeb間を関連付ける
- アプリはXcode上でエンタイトルメント(権利のある)するドメインを選ぶ
- WebサーバはJSONファイルを通じてドメインのどの部分をアプリに表示させるかを選ぶ
- 双方向による紐付けにより第3者によるリダイレクトが防げる
- カスタムURLスキームはセキュリティ面において悪用されるリスクがあるため、ユニバーサルリンクに移行した方がよい
ユニバーサルリンクの実装方法
Webサーバで行うこと
- サーバには有効なHTTPS証明書が必要
- HTTPS証明書に署名するルート証明書はOSの認識が必要で、カスタムルート証明書は使用できない
- Apple App Site Associationファイル(JSONファイル)を追加する
- アプリがインストールされるとこのファイルでアプリが使うサービスが決まります
- ファイルは定期的に更新され、ユニバーサルリンクもこのファイルに含まれます
- ファイルパスの指定
- https://ドメイン名/.well-known/apple-app-site-association と決まっており、他のパスは使用不可
- ファイルには署名が必要なく、署名付きや別パスのJSONファイルは廃止予定
Apple App Site Associate ファイルの詳細
- apps キーはiOS 13以降、macOS 10.15以降であれば不要で、iOS 12以前は必要
- details キーではDictionary形式で記述できていましたが、廃止となった
- appID キーは Appleから支給された英数10文字とbundle IDをピリオドで組み合わせて指定
- 同じユニバーサルリンクを複数のアプリで使用する場合、コードの反復は不要
- また、複数系の appIDs キーが使用可能
- paths キーは components キーに変更され、iOS 13, macOS 10.15以降であればpathsキーは無視される
- URLフラグメント(同一ページ内のリンク)は 「#」を使用する
- URLフラグメントを使用しない場合は不必要
- クエリは 「?」を使用する
- クエリはDictionary形式でのパターンマッチングが可能
- パターンマッチしない文字列は空文字としてみなされる
- Dictionaryと候補のURLのマッチングは全コンポーネントの一致が必須
- URLフラグメント(同一ページ内のリンク)は 「#」を使用する
- exclude キーでアプリにないpathを除外する事ができる
- appID キーは Appleから支給された英数10文字とbundle IDをピリオドで組み合わせて指定
// apple-app-site-association File
{
"applinks": {
"details": [
{
"apps": [],
"appID": "ABCDE12345.com.example.app",
"appIDs": [ "ABCDE12345.com.example.app", "ABCDE12345.com.example.app2" ],
"paths": [ "/path/*/filename" ]
}
]
,
"components": [
{
"/" : "/path/*/filename",
"#" : "*fragment",
"?": { "widget": "?*", "grommet": "please" },
"exclude": true
NEW
}
]
}
}
パターンマッチングの例
// Pattern-Matching Examples
"appIDs": [ "ABCDE12345.com.example.lunch" ],
"components": [
{
"/": "/*/order/" 例:https://example.com/taco/order/ , https://example.com/salad/order/
},
{
"/": "/taco/*",
"?": { "cheese": "?*" } 例:https://example.com/taco/?cheese=panela
},
{
"#": "coupon-1???",
"exclude": true 例:https://example.com#coupon-1234
},
{
"/": "",
"#": "coupon-????" 例:https://example.com#coupon-5678
}
]
- 「*」は空文字を含むどの文字列とも一致します
- 「?*」は1文字以上の文字列との一致
- 上記のサンプルではURLフラグメントで1で始まるものはexcludeしブラウザ側のみ扱うようにし、その他の場合は最後の指定で一致させている
海外ユーザへの対応
- URLと同様にマッチングもASCIIを使用する
- Unicodeでの一致にはURLエンコード行う必要がある
- Unicode文字はASCIIで1文字以上の場合がある
- 国ごとによるApple App Site Associateファイルの区別とダウンロードサイズの削減には下記指定を行う
{ "en", "fr", "mx", … } → "??"
{ "en_US", "fr_CA", "de_CH", … } → "??_??
アプリで行うこと
Associated Domainsの設定
- XcodeからAssociated DomainsのCapabilitiesを追加
- ドメイン先の変更も可能
- エンタイトルメントの値は 「サービスタイプ:ドメイン名」の形式
- サービスタイプは applinks
- アプリがインストールされるとOSが指定したドメインを訪れ、Apple App Site Associationファイルを探し、対象アプリの情報を含むファイルがあれば関連付けが確定する
- ドメインにはワイルドカードの指定が可能
- 正確なドメイン名とワイルドカードの複数を指定している場合は正確なドメインが優先されて検索対象となる
- 国際化ドメインの指定
- URLにはASCIIを使用するため、国際化ドメイン名はPunycodeでのエンコードが必要
- Punycode(ピュニコード、プニコード)とは、国際化ドメイン名で使われる文字符号化方式で、RFC 3492 で定義されている
- URLにはASCIIを使用するため、国際化ドメイン名はPunycodeでのエンコードが必要

<array>
<string>applinks:www.example.com</string>
<string>applinks:*.example.com</string>
<string>applinks:xn--fhqz97e.example.xn--fiqs8s
</array>
URLのパース
- ユニバーサルリンクはNSUserActivityクラスがベースでAppDelegateが処理します
// Configuring Your App
func application(_ application: Application, continue userActivity: NSUserActivity,
restorationHandler: @escaping ([ UserActivityRestoring]?) -> Void) -> Bool {
// ユニバーサルリンクからであることを判定
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
// URLComponentsを使用せず、通常のパースの場合はセキュリティ性が低下するため避ける
let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
// ページを開かない場合(失敗時)
return false
}
for queryItem in components.queryItems ?? [] {
…
}
// ページ開ければtrueを返す
return true
}
macOSでの使用における注意点
- ユニバーサルリンクは通常ブラウザで開かれますが、リンクを押すとアプリでも開けると通知が出て、アプリで開く設定を選ぶと以降は自動的にアプリで開くようになります
- ユニバーサルリンクが機能するのはローカルボリュームのアプリのみで、リモートボリュームは機能しない
- アプリをインストールするとApple App Site Associationファイルのダウンロードも始まる
- Developer IDの署名があるアプリでは一度アプリを起動する必要があります
- ユニバーサルリンクはApp IDと関連付けられているため、Mac上では各リンクにつき1つのアプリが対応します
- 通常は /Applications 内のアプリです
// macOSでのユニバーサルリンクを開く実装
// UIApplication
UIApplication.shared.open(url, options: [ .universalLinksOnly: true ]) {
…
}
// NSWorkspace
let configuration = NSWorkspace.OpenConfiguration()
configuration.requiresUniversalLinks = true
NSWorkspace.shared.open(url, configuration: configuration) {
…
}
ベストプラクティス
- 潔く失敗する
- コンテンツが無効または存在しないURLの場合などでユニバーサルリンクがアプリで開けない時はSafariViewControllerで開ければユーザは良く、無理な場合はSafariで開くかトラブルの詳細を表示して確認する
- ブランクな画面は避けるようにする
- Smart App Bannerの使用する
- App Storeなどへのリンクを提供することでSafariとスムーズに連係可能で、JavascriptやカスタムURLスキームも不要