App Clips を試してみる

この記事は[フラー Advent Calendar 2020](https://adventar.org/calendars/5034) の 20日目の記事です。
19日目は[@nobux42](https://qiita.com/nobux42)さんで「[Next.jsとShopifyで最速でECサイトをつくる](https://qiita.com/nobux42/items/c038fdd5c63334a171c3)」でした。

# App Clips とは

[App Clips](https://developer.apple.com/app-clips/) は WWDC 2020 で発表はされた iOS 14 の新機能です。
まだ中々町中では見かけることは無いので、実際の挙動を確認すべく試した内容を記事としてまとめます。


# 作成方法

Xcode で適当なプロジェクトを作成し、file -> new -> Target... から App Clips を選んで Target に追加します。
![スクリーンショット 2020-12-17 5.08.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82759/c82ef243-d74d-2575-0a06-85f3d561cff2.png)

いくつかのファイルがつくられますが、`ContentView.swift` の中身を書き換えて実行してみます。

```swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Welcome to App Clip")
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

## 実行結果

<img width="320" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82759/6fecaae5-7f32-0d8e-f84d-474e075b0efd.png">

App Clip の起動自体できましたが、肝心のカードが表示されません。

App Clip Card の設定は App Store Connect 上で行うものなので、App Clip 開発時に設定するようなものではありません。
しかし、カードを確認する方法はちゃんと用意されており、今回は App Clips のテスト機能である Local Experiences を使ってカードを表示させてみます。

## Local Experiences の設定

iPhone の設定を開いて Developer -> Local Experiences -> Register Local Experiences... と進むと、URL, Bundle ID と App Clip Card の内容を設定する画面へ遷移するので、適当な URL と設定した Bundle ID を入力し、カード情報を好きに設定します。

<img width="320" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82759/c0d6dcca-8be5-762e-40de-076ad0b467a4.png">

これで保存し、端末に先程の App Clips をインストール (Run すればよい) して準備完了です。
Local Experience で設定した URL を QRコードか NFCタグで読み取ればカードを表示できるようになります。

```sh
$ brew install qrencode
$ qrencode -o - https://example.com | open -f -a preview
```

## 動作確認

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82759/fd9a4a12-44cf-0d08-45a7-d18574eebe20.gif)

コントロールセンターの QRコードリーダ (標準のカメラアプリでは開けない) で、Local Experience で設定した URL を読み取って、先程作成した App Clip が起動できているのが分かります。
この方法は [Associated Domain](https://developer.apple.com/documentation/safariservices/supporting_associated_domains) の設定や `apple-app-site-association` file の配置なども不要なので、カードを見てみたいだけならオススメです。

## 起動URLを取得

App Clip Card の確認はできましたが、開発には起動時の環境変数を指定することで、特定のURLから起動したときの動作を検証することができます。
App Clip では Invocation URL のパラメータを見て処理を分岐させることが可能なので、開発時には使うことが多そうな機能です。
App Clip Target の Edit Schema -> Run -> Arguments を選択し、Environment Variables にテストしたい URL を入力します。

![スクリーンショット 2020-12-18 11.43.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82759/3b5b8181-6a3e-9944-0d26-1ed9819d5c17.png)

上記のように設定した上で、`AppClipSampleClip.swift` で起動URLを取得する処理を実装します。

```swift:AppClipSampleClipApp.swift

@main
struct AppClipSampleClipApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
                    guard let incomingURL = userActivity.webpageURL,
                          let components = NSURLComponents(url: incomingURL,
                              resolvingAgainstBaseURL: true) else {
                        return
                    }

                    print(components.debugDescription)
                }
        }
    }
}
```
出力結果

```
<NSURLComponents 0x6000016ec3b0> {
  scheme = https,
  user = (null),
  password = (null),
  host = example.com,
  port = (null),
  path = /menu,
  query = parameter1=foo&parameter2=bar,
  fragment = (null)
}
```

起動URLが取得できていることが確認できました。


## データ共有

App Clip と本体アプリ間でのデータの共有の受け渡しを実装してみます。
折角なので先程取得した起動時のパラメータを本体アプリに渡してみましょう。

まず、App Groups を設定します。

(Automatically managed signing を利用しています)
Targets で AppClipSample を選び、Signing & Capabilities を選択し、`+ Capability` から App Groups を選択します。
Signing & Capabilities の箇所に App Groups というセクションができているので、+ を押して Group を作成します。
同じ手順で、AppClipSampleClip にも同じ Group を指定します。

同じ Group に設定すると、Shared User Defaults でデータの共有ができるようになるので、それを使います。

```swift:AppClipSampleClipApp.swift
@main
struct AppClipSampleClipApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
                    guard let incomingURL = userActivity.webpageURL,
                          let components = NSURLComponents(url: incomingURL,
                              resolvingAgainstBaseURL: true) else {
                        return
                    }
                    guard let sharedUserDefaults = UserDefaults(suiteName: "group.example.appClipMigration") else {
                        return
                    }
                    sharedUserDefaults.set(components.path, forKey: "path")
                    sharedUserDefaults.set(components.query, forKey: "query")
                }
        }
    }
}
```

```swift:AppClipSample/ContentView.swift
struct ContentView: View {
    var body: some View {
        let sharedUserDefaults = UserDefaults(suiteName: "group.example.appClipMigration")
        let path = sharedUserDefaults?.string(forKey: "path") ?? "faild"
        let query = sharedUserDefaults?.string(forKey: "query") ?? "faild"

        return VStack {
            Text("Full Application")
            Text("path: \(path)")
            Text("query: \(query)")
        }
    }
}
```

`UserDefaults` の `init(suiteName:)` に先程設定した Group名を指定して、保存します。
それを、本体アプリの ContentView で取り出して表示させます。

これで、AppClip を起動したあとに、本体アプリを起動すると、無事にデータを送れたことが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82759/aa5a9ae2-f062-ecdb-83a3-63fc3a96d482.png" width="320px">

App Clip の操作した内容を保存しておいて、ユーザが本体アプリをインストールした場合に利用できるのは、様々な用途で使いそうですね。

ただし、下記のように App Clip からは Keychain にはアクセスできないため、App Clip 側でログインした際の認証情報などを本体アプリに引き継ぐことはできないようです。

> To preserve user privacy across apps and App Clips, an App Clip can only share its data with its corresponding app. In addition, you can’t make information the App Clip stores in the keychain accessible to its corresponding full app.



# 終わりに

簡単にですが、App Clip を試した内容をまとめました。
App Clip はアプリをインストールすること無く、体験を提供することができるので、現実のサービスと連携するようなアプリとかでは特に相性が良いと感じました。
他にも色々な機能があるので、今後も試して紹介していければいいなと思っています。


## 参考

https://developer.apple.com/documentation/app_clips/testing_your_app_clip_s_launch_experience
https://developer.apple.com/documentation/app_clips/making_data_available_to_the_app_clip_s_corresponding_app