記事

Universal Links|iOS 13・14対応の最新動向とローカルテスト環境構築法

iOS 13・14でのUniversal Linksの課題を解決し、ローカルテスト環境を効率的に構築する方法を紹介。開発者が直面するリンク開閉問題を改善し、スムーズなユーザー体験を実現します。

Universal Links|iOS 13・14対応の最新動向とローカルテスト環境構築法

本記事は AI による翻訳をもとに作成されています。表現が不自然な箇所がありましたら、ぜひコメントでお知らせください。

記事一覧


iOS 13、iOS 14 Universal Links の新機能とローカルテスト環境の構築

Photo by [NASA](https://unsplash.com/@nasa?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText){:target="_blank"}

Photo by NASA

はじめに

ウェブサイトとアプリを持つサービスにとって、Universal Linksの機能はユーザー体験において非常に重要であり、Webとアプリ間のシームレスな連携を実現します。しかし、これまで簡単な設定だけであまり深く掘り下げられていませんでした。先日、時間をかけて調査する機会があり、興味深い点をいくつか記録しました。

よくある考慮事項

担当したサービスでは、Universal Linksの実装にあたり、APP上に完全なウェブ機能がないことが多いです。Universal Linksはドメイン名を認識するため、ドメイン名が一致すればAPPが起動します。この問題に対しては、APPに対応していないURLをNOTで除外することができます。もしウェブサービスのURLが極端な場合は、Universal Links専用のサブドメインを新たに作成するのが良いでしょう。

apple-app-site-association はいつ更新される?

  • iOS < 14では、アプリが初めてインストールまたはアップデートされる際に、Universal Linksのサイトのapple-app-site-associationを問い合わせます。

  • iOS ≥ 14では、Apple CDNがUniversal Linksサイトのapple-app-site-associationをキャッシュし、定期的に更新します。アプリは初回インストールや更新時にApple CDNから取得しますが、ここで問題があり、Apple CDNのapple-app-site-associationがまだ古い場合があります。

Apple CDNの更新メカニズムについて調べましたが、ドキュメントには記載がありませんでした。ディスカッションを確認したところ、公式は「定期的に更新される」とのみ回答しており、詳細は後日ドキュメントで公開予定とのことですが、今のところまだ公開されていません。

個人的には最長でも48時間以内に更新されると思います。。。なので次回apple-app-site-associationを変更する場合は、アプリのリリース・アップデート前に数日前からapple-app-site-associationを先に変更しておくことをおすすめします。

apple-app-site-association Apple CDN 確認:

1
2
Headers: HOST=app-site-association.cdn-apple.com
GET https://app-site-association.cdn-apple.com/a/v1/你的網域

現在のApple CDN上のバージョンを取得できます。(Request Headerに Host=https://app-site-association.cdn-apple.com/ を必ず追加してください)

iOS ≥ 14 デバッグ

前述のCDNの問題により、開発段階ではどのようにデバッグすればよいのでしょうか?

幸いにもこの部分はAppleが解決策を提供してくれているので、即時更新ができずに困ることはありません。applinks:domain.com?mode=developer を追加するだけで済みます。その他に、managed(企業内アプリ向け)developer+managed モードも設定可能です。

mode=developer を付けると、APPはシミュレーター上で毎回 Build & Run するたびに、直接サイトから最新の app-site-association を取得して使用します。

実機でBuild & Runする場合は、まず「設定」→「開発者」→「Associated Domains Development」オプションをオンにしてください。

⚠️ ここに落とし穴があります。app-site-association はサイトのルートディレクトリか ./.well-known ディレクトリに置けますが、mode=developer の場合は ./.well-known/app-site-association のみを参照するため、効果がないと思ってしまいました。

開発テスト

もし iOS < 14 で app-site-association を変更した場合は、削除してから再度ビルド&実行しないと最新のものを取得しません。iOS ≥ 14 は前述の方法に加えて mode=developer を使用してください。

app-site-association の内容を変更する場合、できればサーバー上のファイルを直接修正するのが良いですが、サーバー側にアクセスできない場合は、universal links のテストが非常に面倒になります。何度もバックエンドの同僚に頼む必要があり、内容を確定してから一度にリリースしないと、何度も修正を繰り返して同僚を困らせてしまいます。

ローカルにシミュレーション環境を構築する

上記の問題を解決するために、ローカルで小さなサービスを立ち上げることができます。

まずは mac に nginx をインストールします:

1
brew install nginx

もしまだ brew をインストールしていなければ、先にインストールしてください:

1
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

nginxをインストールした後、/usr/local/etc/nginx/ に移動して nginx.conf ファイルを開いて編集します:

1
2
3
4
5
6
7
8
9
10
11
...
server {
        listen       8080;
        server_name  localhost;
#charset koi8-r;
#access_log  logs/host.access.log  main;
location / {
            root   /Users/zhgchgli/Documents;
            index  index.html index.htm;
        }
...

大体44行目あたりで location / 内の root を希望するディレクトリパスに変更してください(ここでは Documents を例とします)。

ポート8080でリッスンします。競合がなければ変更は不要です。

変更を保存した後、以下のコマンドでnginxを起動します:

1
nginx

停止する場合は、以下のようにしてください:

1
nginx -s stop

了解しました。翻訳を停止します。

もし nginx.conf を変更したら、必ず以下を実行してください:

1
nginx -s reload

サービスを再起動する。

./.well-known ディレクトリを先ほど設定した root ディレクトリ内に作成し、apple-app-site-association ファイルを ./.well-known 内に配置します。

⚠️ .well-known フォルダを作成した後に消えた場合は、Macで「隠しフォルダを表示」機能をオンにしてください:

ターミナルで:

1
defaults write com.apple.finder AppleShowAllFiles TRUE

killall finder を実行すると、すべての Finder が再起動します。

⚠️ apple-app-site-association は拡張子がないように見えますが、実際には .json 拡張子があります:

ファイルを右クリック -> 「情報を見る Get Info」-> 「名前と拡張子 Name & Extension」-> 拡張子があるか確認し、同時に「拡張子を隠す Hide extension」のチェックを外すことができます

問題なければ、ブラウザを開いて以下のリンクが正常に apple-app-site-association をダウンロードできるか確認してください:

1
http://localhost:8080/.well-known/apple-app-site-association

正常にダウンロードできれば、ローカル環境のシミュレーションが成功したことを意味します!

もし404や403エラーが発生した場合は、ルートディレクトリが正しいか、ディレクトリやファイルが配置されているか、apple-app-site-associationに誤って拡張子(.json)が付いていないかを確認してください。

登録&ダウンロード Ngrok

[ngrok.com](https://dashboard.ngrok.com/get-started/setup){:target="_blank"}

ngrok.com

ngrok実行ファイルの解凍

ngrok実行ファイルを解凍する

DashboardページにアクセスしてConfig設定を実行

Dashboard ページ にアクセスして、Config 設定を実行します。

1
./ngrok authtoken あなたのTOKEN

設定が完了したら、次に進みます:

1
./ngrok http 8080

私たちのnginxは8080ポートで動作しています。

サービスを起動する。

この時、サービス起動状態のウィンドウが表示され、Forwardingから今回割り当てられた公開URLを取得できます。

⚠️ 起動するたびに割り当てられるURLが変わるため、開発テストのみに使用してください。

ここでは今回割り当てられたURL https://ec87f78bec0f.ngrok.io/ を例にします

ブラウザに戻り、https://ec87f78bec0f.ngrok.io/.well-known/apple-app-site-association を入力して、apple-app-site-associationファイルが正常にダウンロード・閲覧できるか確認してください。問題なければ次のステップに進めます。

ngrokで割り当てられたURLをAssociated Domainsのapplinks:設定に入力します。

?mode=developer を付けておくとテストが便利です。

アプリの再ビルド&実行:

ブラウザを開き、対応する Universal Links テストURL(例: https://ec87f78bec0f.ngrok.io/buy/123)を入力して動作を確認します。

ページに404が表示されても無視してください。実際にはそのページは存在しません。私たちはiOSのURLマッチング機能が期待通りかどうかをテストしているだけです。上部に「Open」と表示されていればマッチ成功を意味します。また、NOTの逆パターンもテスト可能です。

「Open」をクリックしてアプリを開く -> テスト成功!

開発段階で問題なくテストが完了したら、修正したapple-app-site-associationファイルをバックエンドに渡してサーバーにアップロードすれば、問題なく動作することが保証されます〜

最後にAssociated Domainsのapplinks:を正式なサイトのURLに変更してください。

また、ngrokの実行状態ウィンドウから、毎回のAPPビルド&実行時にapple-app-site-associationファイルが取得されているか確認できます:

iOS 13 未満:

設定ファイルはより簡単で、以下の内容のみ設定可能です:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "applinks": {
      "apps": [],
      "details": [
           {
             "appID" : "TeamID.BundleID",
             "paths": [
               "NOT /help/",
               "*"
             ]
           }
       ]
   }
}

TeamID.BundleId をあなたのプロジェクト設定に置き換えてください(例:TeamID = ABCD 、BundleID = li.zhgchg.demoapp => ABCD.li.zhgchg.demoapp)。

複数の appID がある場合は、複数回追加してください。

paths 部分はマッチングルールで、以下の構文をサポートしています:

  • * :0文字以上にマッチします。例:/home/*(home/alan…)

  • ? :1文字にマッチします。例:201?(2010〜2019)

  • ?* :1文字以上にマッチします。例:/?*(/test、/home など)

  • NOT :除外指定。例:NOT /help(/help以外のすべてのURL)

より多くの組み合わせは実際の状況に応じて自由に決められます。詳細は公式ドキュメントをご参照ください。

- ご注意ください、これは正規表現ではなく、正規表現の構文はサポートされていません。

- 古いバージョンはQuery (?name=123)やAnchor (#title)をサポートしていません。

- 中国語のURLは先にASCIIに変換してからpathsに入れる必要があります(すべてのURL文字はASCIIである必要があります)。

iOS ≥ 13 以降:

設定ファイルの機能が強化され、Query/Anchor、文字セット、エンコーディング処理のサポートが追加されました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"applinks": {
  "details": [
    {
      "appIDs": [ "TeamID.BundleID" ],
      "components": [
        {
          "#": "no_universal_links",
          "exclude": true,
          "comment": "フラグメントが no_universal_links と等しいURLにマッチし、システムにユニバーサルリンクとして開かないよう指示します"
        },
        {
          "/": "/buy/*",
          "comment": "/buy/で始まるパスを持つURLにマッチします"
        },
        {
          "/": "/help/website/*",
          "exclude": true,
          "comment": "/help/website/で始まるパスを持つURLにマッチし、システムにユニバーサルリンクとして開かないよう指示します"
        },
        {
          "/": "/help/*",
          "?": { "articleNumber": "????" },
          "comment": "/help/で始まるパスを持ち、クエリ項目に名前が 'articleNumber' で値が正確に4文字のURLにマッチします"
        }
      ]
    }
  ]
}

公式ドキュメントからの転載で、フォーマットが変更されたことがわかります。

appIDs は配列で、複数の appID を入れることができます。これにより、以前のように同じブロックを繰り返し入力する必要がなくなります。

WWDCでは互換性について言及されており、iOS 13以降では新しいフォーマットを読み取ると古いpathsは無視されます

マッチングルールは components に移動され、3種類のタイプをサポートしています:

  • / : URL

  • ? :クエリ、例:?name=123&place=tw

  • # :アンカー、例:#title

そして組み合わせて使用することも可能で、例えば /user/?id=100#detail の場合にのみアプリを起動したい場合は、以下のように記述できます:

1
2
3
4
5
{
  "/": "/user/*",
  "?": { "id": "*" },
  "#": "detail"
}

そのマッチング構文は元の構文と同じで、* ? ?* もサポートしています。

comment コメント欄を追加しました。コメントを入力して識別しやすくできます。(ただしこれは公開されるので、他の人も見えます)

逆除外は exclude: true を指定してください。

caseSensitive 指定機能を追加しました。マッチングルールが大文字小文字を区別するかどうかを指定できます。
デフォルトは true です。必要に応じて設定することで、多くのルールを省略できます。

percentEncoded の追加についてですが、旧バージョンではURLを先にASCIIに変換してpathsに入れる必要がありました(中国語などは見た目が悪く判別しにくくなります)。このパラメータは自動的にエンコードするかどうかを指定するもので、デフォルトは true です。
例えば中国語のURLでもそのまま入れることができます(例: /客服中心)。

詳細な公式ドキュメントはこちらをご参照ください。

デフォルトの文字セット:

これは今回のアップデートで重要な機能の一つであり、文字セットのサポートが追加されました。

システムが定義した文字セット:

  • $(alpha) :A〜Z と a〜z

  • $(upper) :A-Z

  • $(lower) :a-z

  • $(alnum) :A-Z と a-z と 0–9

  • $(digit) :0–9

  • $(xdigit) :16進数の文字、0–9 と a,b,c,d,e,f,A,B,C,D,E,F

  • $(region) :ISO地域コード isoRegionCodes 、例:TW

  • $(lang) :ISO言語コード isoLanguageCodes 、例:zh

多言語対応のURLがある場合、Universal Linksをサポートするには、以下のように設定できます:

1
2
3
"components": [        
     { "/" : "/$(lang)-$(region)/$(food)/home" }      
]

これで /zh-TW/home/en-US/home のどちらもサポートでき、とても便利で、ルールを一つ一つ書く必要がありません!

カスタム文字セット:

デフォルトの文字セットに加えて、カスタム文字セットを作成して設定ファイルの再利用性と可読性を向上させることもできます。

applinkssubstitutionVariables を追加するだけでOKです:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "applinks": {
    "substitutionVariables": {
      "food": [ "burrito", "pizza", "sushi", "samosa" ]
    },
    "details": [{
      "appIDs": [ ... ],
      "components": [
        { "/" : "/$(food)/" }
      ]
    }]
  }
}

例ではカスタムの food 文字セットを定義し、その後の components で使用しています。

上記の例は /burrito/pizza/sushi/samosa にマッチします。

詳細はこちらの公式ドキュメントをご参照ください。

アイデアが浮かばない?

設定ファイルの内容にアイデアがない場合は、他のサイトの内容をこっそり参考にしても構いません。サービスサイトのトップページのURLに /app-site-association または /.well-known/app-site-association を追加するだけで、その設定を読むことができます。

例えば: https://www.netflix.com/apple-app-site-association

補足

SceneDelegate を使用している場合、ユニバーサルリンクを開く入口は SceneDelegate 内にあります:

1
func scene(_ scene: UIScene, continue userActivity: NSUserActivity)

AppDelegate ではなく:

1
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool

関連記事

参考資料

ご質問やご意見がございましたら、こちらからご連絡ください

PostZMediumToMarkdown によって Medium から変換されました。


🍺 Buy me a beer on PayPal

👉👉👉 Follow Me On Medium! (1,053+ Followers) 👈👈👈

本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。

Improve this page on Github.

本記事は著者により CC BY 4.0 に基づき公開されています。

© ZhgChgLi. All rights reserved.
閲覧数: 802,415+, 最終更新日時: 2026-01-15 11:14:58 +08:00

本サイトは Chirpy テーマを使用し、Jekyll 上で構築されています。
Medium の記事は ZMediumToMarkdown により変換されています。