App Clipsに対応する

15 min read読了の目安(約14000字

先日公開された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が起動されます。

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

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

他の特徴として、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で決済できます。

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

機能面でみれば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を選択すると、コンパイル後のアプリのサイズを見ることができます。

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上で行います。

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

アプリの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が含まれていると、ドメインが有効かをチェックされます。

これはダメな例

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

これは有効な例

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への遷移導線がすでに備わっています。

  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が表示されます。

まとめ

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

おまけ

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

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

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

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

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

References