📎

App Clipsに対応する

2020/10/22に公開約14,600字

先日公開された iOS 14 では Widget や App Library, Picture in Picture といったユーザーインタラクション面での多くのアップデートが公開されましたが、そのラインナップのひとつに App Clips があります。これは、App Store でのアプリをインストールすることなく、コードを読み込むだけでアプリの一部の機能を持ったものがその場で起動できるというものです。この記事では、実装から公開までのカンカク社の COFFEE App を例にを解説します。

より一般向けにはこちらで会社から記事をリリースしています。よければご一読ください

App Clips とは

App Clips は生活上のあらゆるところに App Clips の起動のトリガーを設置することで、その場でアプリの体験の一部を提供するものです。

起動には QR コードや NFC のようにあらかじめ設置したものに端末を近づける方法や、Web サイトに設置された Safari Smart Banner、Apple Map、iMessage でのリンク送信があります。読み込むと、下図のように App Clip Card と呼ばれるカードが表示され、開くことで App Clip が起動されます。

qf1b5rudd8cvtr28hc63ai1n8xpe

App Clip Code という独自の形式のコードも存在しますが、この提供は年内になると言われており、今後発表される新デバイスにも期待が膨らみます。

あらかじめ設定するのは Invocation URL ですが、これを Safari で直接開いても App Clip Card は起動しません。

k3jip46lw5xicmhcetrcr2h19lst

他の特徴として、Sign in with Apple や Apple Pay が使える、8 時間以内に送れる Notification などがあります。アプリと同じですが、Keychain など使えない機能があることに気をつけます。

App Clips はその実態もアプリと同じで、拡張子も.app です。これをアプリ本体に Embed して App Store Connect にアップロードします。

App Clips の体験を設計する

App Clips の思想から、その場ですぐに使える、使い捨てることを想定して体験を設計していくのがよいでしょう。

KITASANDO COFFEE、TAILORED CAFE で提供する App Clips は、来店時に App Clips を起動することを想定し、読み込むとすぐにメニューが立ち上がり、会員登録なしで Apple Pay で決済できます。

アプリ本体は繰り返し使うことを想定していて機能設計しているので、繰り返し来店すると便利に使えるように機能を搭載しています。

9hm7kbeain0lngka57v09f0ce7bj

機能面でみれば App Clips はただのアプリの一部の機能ですが、使い切りのアプリとして体験を設計しなおすと街中でとても便利に使えるものになるのではないでしょうか。

また、App Store Review Guideline の項目も App Clips 向けに改訂されているので、設計時に一読されることをおすすめします。

[https://twitter.com/d_date/status/1305343471352385537?s=20]

[https://twitter.com/d_date/status/1305346614710513665?s=20]

App Clips に対応する

App Clips の対応でやるべきことは次の項目です。

  • App Clips のターゲットを作成
  • App 本体とソースコードやリソースを共有する
  • App Clips のバイナリを非圧縮で 10MB 以下にする
  • App Clips Experience の準備
  • (Advanced App Clips Experience の準備)
  • App Clips Experience をデバッグする

App Clips のターゲットを作成する

App Clips のターゲットは、プロジェクトに通常のターゲットを追加する要領で作成します。

Beta の時点で XcodeGen でも作成できるようにコミュニティが対応を進めました。関連する PR はこの辺です。

[https://github.com/tuist/XcodeProj/pull/554]

[https://github.com/yonaskolb/XcodeGen/pull/908]

[https://github.com/yonaskolb/XcodeGen/pull/909]

⚠️ Xcode からターゲットを追加した場合は AppClip.framework がリンクされています。XcodeGen でプロジェクトを生成する場合はこれを明示的に dependencies に追加する必要があり、リンクしないまま NSUserActivity.appClipActivationPayload にアクセスすると unrecognized selector でランタイムクラッシュします。

App 本体とソースコードやリソースを共有する

App Clips はその機能がアプリに含まれていることが条件としてあります(App Store Review Guideline 2.5.16) 。よって、多くの場合は App 本体と同じような実装を行うことになりますが、App 本体とソースコードやリソースを共有するのがよいでしょう。

Apple のドキュメントにも、コードを共有するためにリファクタリングするいい機会と書かれていますが、取りうる方針は主に3つあります。

  1. Shared framework を作る
  2. ソースコードを両方のターゲットに追加する
  3. ソースコードの共有を諦める

1 や2の方針を取る場合は、Active Compilation Conditions に APPCLIP のような Build Setting 定義して、共有コード内で分岐することも可能です。これは App Clips 専用に予め用意されたものではなく、一般にどのターゲットでも行える方法です。

#if !APPCLIP
// Code you don't want to use in your app clip.
#else
// Code your app clip may access.
#endif

しかし、はじめからソースコードの共有を行おうとすると、App 本体と App Clips の両方のことを念頭におきながらリファクタリングする必要があります。これは難しい作業になりがちです。必要に応じて3のように一旦わけて考えることも選択肢にいれておくと進めやすいです。

App Clips のサイズを削減する

App Clips の制約の中で最も意識することはバイナリサイズです。非圧縮の状態で 10MB 以下にする必要があります。

アプリのサイズの確認の方法でよくある誤解は、生成された ipa ファイルのサイズがそのままインストールサイズとなるというものです。

bitcode が有効になっているアプリでは、App Store Connect にバイナリをアップロードすると、App Store Connect 上で再度コンパイルが走り、それぞれのデバイスに最適化された ipa が配布されることになっています(App Thining)。

App Store Connect にアップロードしたあとは、Activity -> (任意のビルド選択) -> App Store File Sizeを選択すると、コンパイル後のアプリのサイズを見ることができます。

54rsl4y0y743t5i9gjtd4xyvvy6e

Download Size は圧縮時のサイズです。Universal Binary では 8.98MB ですが、各デバイスでは約 4MB となっています。Install Size はデバイス上で展開されたあとのアプリのサイズです。App Clips での 10MB という制限はこの Install Size が該当します。

ここでみているのは App 本体のバイナリサイズで App Clips のものではありません。記事公開時点では、App Store Connect 上で App Clips のバイナリサイズを調べる術はありません。では、どのようにサイズを確認すればよいでしょう。

Xcode で ipa をアップロードする

fastlane や他の CI/CD ツールの普及により、Xcode から Archive を選択するという経験をしている Developer が以前より減っているのかもしれませんが、ストアへのアップロード作業は Xcode で完結します。

自動化のワークフローに組み込む

Organizer でできるということは、xcodebuild のコマンドでもサイズの確認が可能であることを示唆します。

xcodebuild -exportArchive -archivePath iOSApp.xcarchive -exportPath Release/MyApp -exportOptionsPlist OptionsPlist.plist

exportOptionPlist に指定する OptionsPlist には thinning キーと対応する値として<thin-for-all-variants>が含まれていることが必要です。

生成する際は、<thin-for-all-variants> のアングルブラケットは、それぞれ<と>とエスケープすることに注意します

実際にこのようなレポートが出力されており、App Thining されているものと、Universal バイナリでそれぞれ非圧縮で 5.9MB であることがわかります。

App Thinning Size Report for All Variants of CoffeeAppStaging

Variant: CoffeeClip-1C463407-822E-4CEC-A7BE-24239F67CF97.ipa
Supported variant descriptors: [device: iPhone11,8, os-version: 14.0] and [device: iPhone12,1, os-version: 14.0]
App + On Demand Resources size: 2.1 MB compressed, 5.9 MB uncompressed
App size: 2.1 MB compressed, 5.9 MB uncompressed
On Demand Resources size: Zero KB compressed, Zero KB uncompressed

Variant: CoffeeClip-22D5971D-13E5-46F0-89E7-924D06C369B6.ipa
Supported variant descriptors: [device: iPhone9,4, os-version: 14.0], [device: iPhone12,5, os-version: 14.0], [device: iPhone11,2, os-version: 14.0], [device: iPhone12,3, os-version: 14.0], [device: iPhone9,2, os-version: 14.0], [device: iPhone10,2, os-version: 14.0], [device: iPhone10,6, os-version: 14.0], [device: iPhone11,4, os-version: 14.0], [device: iPhone11,6, os-version: 14.0], [device: iPhone8,2, os-version: 14.0], [device: iPhone10,5, os-version: 14.0], and [device: iPhone10,3, os-version: 14.0]
App + On Demand Resources size: 2.1 MB compressed, 5.9 MB uncompressed
App size: 2.1 MB compressed, 5.9 MB uncompressed
On Demand Resources size: Zero KB compressed, Zero KB uncompressed

Variant: CoffeeClip-C07CFD24-D5EE-4B13-A1FA-6591541F93CF.ipa
Supported variant descriptors: [device: iPhone12,8, os-version: 14.0], [device: iPhone10,4, os-version: 14.0], [device: iPhone9,3, os-version: 14.0], [device: iPhone8,1, os-version: 14.0], [device: iPhone10,1, os-version: 14.0], [device: iPod9,1, os-version: 14.0], [device: iPhone8,4, os-version: 14.0], and [device: iPhone9,1, os-version: 14.0]
App + On Demand Resources size: 2.1 MB compressed, 5.9 MB uncompressed
App size: 2.1 MB compressed, 5.9 MB uncompressed
On Demand Resources size: Zero KB compressed, Zero KB uncompressed

Variant: CoffeeClip-FC27667F-D716-4F1F-8864-8949B4C97A00.ipa
Supported variant descriptors: [device: iPad7,12, os-version: 14.0], [device: iPad7,11, os-version: 14.0], [device: iPad7,2, os-version: 14.0], [device: iPad7,6, os-version: 14.0], [device: iPad5,1, os-version: 14.0], [device: iPad5,2, os-version: 14.0], [device: iPad7,3, os-version: 14.0], [device: iPad8,2, os-version: 14.0], [device: iPad8,3, os-version: 14.0], [device: iPad6,3, os-version: 14.0], [device: iPad6,7, os-version: 14.0], [device: iPad11,3, os-version: 14.0], [device: iPad7,4, os-version: 14.0], [device: MacFamily20,1, os-version: 14.0], [device: iPad6,4, os-version: 14.0], [device: iPad11,4, os-version: 14.0], [device: iPad8,4, os-version: 14.0], [device: iPad8,1, os-version: 14.0], [device: iPad7,1, os-version: 14.0], [device: iPad8,9, os-version: 14.0], [device: iPad8,5, os-version: 14.0], [device: iPad8,11, os-version: 14.0], [device: iPad11,1, os-version: 14.0], [device: iPad8,7, os-version: 14.0], [device: iPad11,2, os-version: 14.0], [device: iPad6,11, os-version: 14.0], [device: iPad8,6, os-version: 14.0], [device: iPad6,8, os-version: 14.0], [device: iPad8,12, os-version: 14.0], [device: iPad8,8, os-version: 14.0], [device: iPad5,3, os-version: 14.0], [device: iPad5,4, os-version: 14.0], [device: iPad7,5, os-version: 14.0], [device: iPad6,12, os-version: 14.0], and [device: iPad8,10, os-version: 14.0]
App + On Demand Resources size: 2.1 MB compressed, 5.9 MB uncompressed
App size: 2.1 MB compressed, 5.9 MB uncompressed
On Demand Resources size: Zero KB compressed, Zero KB uncompressed

Variant: CoffeeClip.ipa
Supported variant descriptors: Universal
App + On Demand Resources size: 2.1 MB compressed, 5.9 MB uncompressed
App size: 2.1 MB compressed, 5.9 MB uncompressed
On Demand Resources size: Zero KB compressed, Zero KB uncompressed

Apple Developer の Reduce Your App's Size という記事にこれらの情報がまとまっています。

[https://developer.apple.com/documentation/xcode/reducing_your_app_s_size]

10MB をオーバーしていて、アプリの容量を削減する必要がある場合、サイズに大きく寄与する Asset の確認をしてみることをおすすめします(実際に 5MB の PDF が発掘されて、アプリ本体の容量も削減されました)。

App Clip Experience を設定する

App Store Connect 上で、App Clip Card の設定をします。ここでやっているのは Safari の Smart App Banner で表示されるものです。(弊社では使うことはないのですが、設定しないと申請できないのでとりあえず設定します)。

地図と紐づける、複数の店舗で違う URL をセットするなどの場合は、次の Advanced App Clip Experience へ進みます。

Advanced App Clip Experience を設定する

Advanced App Clip Experience とは、App Clip Experience を複数設定できるというものです。ユースケースとしては、App Clips を複数のロケーションでそれぞれ別の URL を設定する場合です。

COFFEE App では、店舗を選択するユーザーの行動をなくすために、各店舗には予め各店舗の情報を入れた QR コード、NFC タグを設置しています。

設定は事前に App Store Connect 上で行います。

u8wraqn8cjd2sr95hv3ich1mwjzl

複数お店を持っていて、App Clips は地図と紐づける場合はこういう表示になります。

zksm2zyd14n04oc5g61ucpdemtmx

アプリの Associated Domains を設定する

アプリ本体と App Clips 両方に次の Associated Domain を設定します。

appclips:<fully qualified domain>

apple-app-site-association(AASA) ファイルに Associated Domains の設定をする

"appclips":{
  "apps":["<TEAM ID>.<Bundle Identifier of App Clip>"]
}

バイナリを App Store Connect にアップロードする

iOS 14 から AASA ファイルの仕組み

これまで、AASA ファイルというのは主に Universal Links に使われるものであり、URL にアクセスする -> サイトの AASA ファイルを見る -> 該当する Bundle Identifier をみるという挙動ですが、iOS 14 からはこの App Clips が導入されたからなのか、Apple の CDN がこのファイルを事前にキャッシュすることになっています。

Starting with macOS 11 and iOS 14, apps no longer send requests for apple-app-site-association files directly to your web server. Instead, they send these requests to an Apple-managed content delivery network (CDN) dedicated to associated domains.

[https://developer.apple.com/documentation/safariservices/supporting_associated_domains]

バイナリをアップロードすると、Associated Domain にappclipsが含まれていると、ドメインが有効かをチェックされます。

これはダメな例
6lmc38fe6ye29uebl9ygyc3z2ed9

これは修正して Load Debug Status を押したら有効になったけど、キャッシュが更新されていない例
iecl59cqiio3k29j1fl1zkbn7z3b

これは有効な例
fkgjn4dyilrurso1r4xa7qz3og18

Advanced App Clip Experience の実装

起動時に Invocation URL を取り扱う場合には注意が必要です。
初回起動時と、Recently Used App Clips を起動した場合で呼ばれるメソッドが異なります。

1. 初回起動時

SceneDelegate の scene(willConnectTo:session)の第三引数 connectionOptions.userActivities が appClipActivationPayload を持っています。URL を特に設定していなければここには example.com が渡りますが、後述する Invocation URL を事前にセットしたデバッグでは、ここに設定した URL が渡ってきます。

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
       if let windowScene = scene as? UIWindowScene {
           ...

           connectionOptions.userActivities.forEach { userActivity in
               if let url = userActivity.appClipActivationPayload?.url {
                   self.handleAppClip(url)
               }
           }
       }
   }

2. Recently Used App Clips を起動した場合

iOS 14 のホーム画面を左スワイプしていくと、App ライブラリという項目に App Clips が表示されます(これは Recently Used App Clips といいます)。これをタップした場合は scene(continue userActivity)が呼ばれます。

  func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
       if let url = userActivity.appClipActivationPayload?.url {
           self.handleAppClip(url)
       }
   }

App Clip Experience をデバッグする

App Clips の挙動をテストするには、URL を環境変数を埋め込んでおく、Local Experience を設定する、Test Flight で配信時に URL を設定するの3通りあります。

URL を環境変数に設定する

開発中に使える方法です。

URL をScheme -> Run -> Environment Variables_XCAppClipURL を Key として設定します。

Debug App Clips using the scheme created for the App Clip. In the scheme the environment variable _XCAppClipURL can be used to set the App Clip experience URL for the debugging session. (59404002)

Local Experience を設定する

App Clip Card の表示の確認と対応する Invocation URL を開いて挙動を確認するために、Developer Mode が有効なデバイスで行えるテストです。

  1. 設定アプリのデベロッパ -> App Clips Testing -> Local Experiences -> Register Local Experiences…を開く
  2. URL PREFIXに Invocation URL を設定する
  3. BUNDLE IDに App Clips の Bundle Identifier を設定する
  4. Title, Subtitle, Action, 画像は任意で設定を終わる
  5. QR コードや、NFC を読み取る。Quick Action では、カメラではなく、QR コードリーダーを起動する
  6. App Clip Card が表示される

App Clips から App のインストールを訴求する

App Clips から App 本体へ誘導する方法には二種類あります。

  1. App Clip Card から App Store へ遷移
    App Clip を QR コードなどで起動した際に表示される App Clip Card には App Store への遷移導線がすでに備わっています。

1x142agiryg2yaat2a54hx6byeq9

  1. SKOverlay を表示する
    App Clips 内から App 本体へ誘導したい場合は SKOverlay を利用します。

SKOverlay は iOS 14 から導入された、他のアプリや App Clips に対応するアプリ本体へのインストール訴求を行うためのレイヤーです。

[https://developer.apple.com/documentation/app_clips/recommending_an_app_clip_s_corresponding_app]

let configuration = SKOverlay.AppClipConfiguration(position: .bottom)
if let windowScene = self.view.window?.windowScene {
  SKOverlay(configuration: configuration).present(in: windowScene)
}

上記のように、任意のタイミングで表示を行うことができ、次のような Overlay が表示されます。

ozmws5lunl0pqr71vs5kzeazk1va

まとめ

App Clips に対応する手順を事例とともに解説しました。アプリの機能の一部を切り出して体験させることがユーザーにとってメリットになりうるかは一概に述べることはできませんが、対応される際にはご一読ください。

おまけ

1. Test Flight の Invocation URL を変更できない

先述の Test Flight で App Clips をテストするために URL を設定するのですが、設定後に変更しても反映されません。諸事情でドメインを変更したのですが、その変更が効いてないので、アプリを開いても URL が渡ってきません

変更するには、一回追加した Invocation を削除してからまた追加します。

wl9km3wz9aedwb9okxe71w69kee5

2. iOS 14.0.x で App Clips を利用できない

iOS 14.0.x ではヘッダー画像は表示されますが、App Clips が利用できない状態でした。
iOS 14.1 では利用できることを確認できました(このため、店頭での利用が遅れました)。
どのような条件で利用できなかったのが今もわからず、対応してリリースしてから2〜3週間程度利用できませんでした。

References

Discussion

ログインするとコメントできます