【iOS13】scene(_:willConnectTo:options:)のオプションとセッションの NSUserActivity の違いについて

【iOS13】scene(_:willConnectTo:options:)のオプションとセッションの NSUserActivity の違いについて

## はじめに
`scene(_:willConnectTo:options:)`での`NSUserActivity`からのシーンの復元処理について、多くのAppleのテンプレートでは以下のようになっています。

```Swift
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
            // UIの復元処理を行う
        }
    }
```
この時に、`session`または`connectionOptions`から得られる`NSUserActivity`の違いがよくわからなったので調べたことを、まとめておきたいと思います。

長くなったので、先にまとめを記載しておきます。

## まとめ
- `scene(_:willConnectTo:options:)`の`connectionOptions`はシステム側でのシーン作成時に、デリゲートへのパラメータ受け渡しに使われる。
- ただし`connectionOptions.userActivities`には、Handoffに関するユーザーアクティビティは入ってこない。`handoffUserActivityType`にタイプ情報だけが提供される。
- Handoffのユーザーアクティビティは`scene(_:willContinueUserActivityWithType:)`という、専用のデリゲートメソッドに入ってくる。
- Appleのテンプレート通りの実装なら、`connectionOptions.userActivities.first`も`session.stateRestorationActivity`にも同じユーザーアクティビティが入ってきそう。

## WWDC2019-212

まず、[WWDC2019-212の中盤](https://developer.apple.com/videos/play/wwdc2019/212?time=1551)で`ConnectionOptions#userActivities`について言及されている部分を抜き出します。

- [connectionOptions.userActivities](https://developer.apple.com/documentation/uikit/uiscene/connectionoptions/3238074-useractivities)

> Now, in this template that I pulled out, we're already looking for any user activities that we have gotten through Handoff or any other system facilities that vend us user activities. We want to prefer those user activities because that's what the user actually did.
[WWDC2019-212 Transcript](https://developer.apple.com/videos/play/wwdc2019/212?time=1551)

- [session.stateRestorationActivity](https://developer.apple.com/documentation/uikit/uiscenesession/3197959-staterestorationactivity?language=swift)

> But the stateRestorationActivity is special and it's on the session. So if you don't have one of those activities, we want to use our stateRestorationActivity.
[WWDC2019-212](https://developer.apple.com/videos/play/wwdc2019/212?time=1551)

とあり、「Handoffまたは他のシステム機能を介して取得したユーザーアクティビティ」が`connectionOptions.userActivities`に入っているように言われています。次点で、セッションが保持している`stateRestorationActivity`からユーザーアクティビティを取得するようです。**もっと詳しいところをドキュメントで調べました。**

## APIドキュメントではどうか
まず`UIScene.ConnectionOptions`から見てみます。

- [UIScene.ConnectionOptions](https://developer.apple.com/documentation/uikit/uiscene/connectionoptions)

> A data object containing information about the reasons why UIKit created the scene.
> UIKit creates scenes for many reasons. It might do so in response to a Handoff request or a request to open a URL. When there is a specific reason for creating a scene, UIKit fills a UIScene.ConnectionOptions object with the associated data and passes it to your delegate at connection time.

「Handoff」や「URLを開くリクエスト」など、システム側でシーンが作成される時、`UIScene.ConnectionOptions`にパラメータを詰めてデリゲートに渡されるようです。
しかしながら、このオプションの`userActivities`プロパティの説明には、

- [UIScene.ConnectionOptions#userActivities](https://developer.apple.com/documentation/uikit/uiscene/connectionoptions/3238074-useractivities) 

> This property does not contain user activity objects related to Handoff. 

とあり、**Handoffに関するユーザーアクティビティは含まれないとあります。**続いて、

> UIKit delivers only the type of a Handoff interaction in the handoffUserActivityType property. Later, it calls additional methods of the delegate to deliver the NSUserActivity object itself.

とあり、シーンが起動された際に**`NSUserActivity`の`activityType`として[UIScene.ConnectionOptions#handoffUserActivityType](https://developer.apple.com/documentation/uikit/uiscene/connectionoptions/3238070-handoffuseractivitytype) のタイプ情報のみが提供される**ようです。
その後、**別のデリゲートメソッドにHandoffのユーザーアクティビティは渡される**とのこと。「additional methods of the delegate」が示すところを探してみると、

- [scene(_:willContinueUserActivityWithType:)](https://developer.apple.com/documentation/uikit/uiscenedelegate/3238060-scene)

> Tells the delegate that it is about to receive Handoff-related data.

と**シーンベースでHandoffのユーザーアクティビティを受け渡しする専用デリゲートメソッドが用意されてました。**WWDCの動画での言及内容のみですと、この辺はやや誤解してしまう印象があります。

ちなみに、**アプリケーション単位でHandoffでのユーザーアクティビティの受け渡しを行うのは [application(_:continue:restorationHandler:)](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623072-application) というAPI**があります。

で、結局`connectionOptions.userActivities`プロパティには、何のユーザーアクティビティが入っているかというと、

> You create user activity objects at key moments in your scene's lifetime and register them with the system. 

と続き、**自分でシステムに対し明示的に登録したユーザーアクティビティ**のようです。この登録に使用するAPIは、どれのことでしょうか。これは`UISceneDelegate#stateRestorationActivity(for:)`に記載がありました。

- [stateRestorationActivity(for:)](https://developer.apple.com/documentation/uikit/uiscenedelegate/3238061-staterestorationactivity)

> When reconnecting the scene later, UIKit includes the NSUserActivity object in the UIScene.ConnectionOptions passed to your scene delegate's scene(_:willConnectTo:options:) method.

とあり、この**stateRestorationActivity(for:)を介して渡したユーザーアクティビティが、デリゲートの`scene(_:willConnectTo:options:)`での`connectionOptions`引数に入る**ようです。
てっきり`stateRestorationActivity(for:)`で保存したユーザーアクティビティが、APIの命名的に対になっていそうな`session.stateRestorationActivity`に入ると思いましたが、では`session.stateRestorationActivity`には何が入るか？

- [UISceneSession#stateRestorationActivity](https://developer.apple.com/documentation/uikit/uiscenesession/3197959-staterestorationactivity?language=swift)

> Before disconnecting a scene, the system asks your delegate for an NSUserActivity object containing state information for that scene. If you provide that object, the system puts a copy of it in this property. 

とドキュメントにあり、アプリ切断時にデリゲートを通してシステムに提供したNSUserActivityが、再接続時にこのプロパティにコピーされるとのこと。

UISceneDelegate で NSUserActivity をシステムに提供できるのは、

- [UISceneDelegate](https://developer.apple.com/documentation/uikit/uiscenedelegate)

> stateRestorationActivity(for:)
> Returns a user activity object encapsulating the current state of the specified scene.

と記載のある`stateRestorationActivity(for:)`のことだと思われます。Appleのテンプレート通りの実装では、

```Swift
    func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
        return scene.userActivity
    }
```

とします。つまり**この場合、`connectionOptions.userActivities.first`も`session.stateRestorationActivity`のどちらにも同じユーザーアクティビティが入ってきそう**です。

確かに、次の項目で紹介するAppleのサンプルコードで検証した感じではそのようでした。

<img width="871" alt="スクリーンショット 2019-10-28 14.55.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53174/aa9db7cd-fdf7-fd60-34f6-9fdb94e95b13.png">

## シーンのユーザーアクティビティを保存する方法

付随して、サンプルコードで見つけたユーザーアクティビティの保存に関するパターンも追記しておきます。

・パターン１

[Sample Code - Supporting Multiple Windows on iPad](https://developer.apple.com/documentation/uikit/app_and_environment/scenes/supporting_multiple_windows_on_ipad)

```Swift:特定のViewController内で
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        view.window?.windowScene?.userActivity = photo?.openDetailUserActivity
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        view.window?.windowScene?.userActivity = nil
    }
```

・パターン２

[SampleCode - Restoring Your App’s State](https://developer.apple.com/documentation/uikit/uiviewcontroller/restoring_your_app_s_state)

```Swift:UISceneDelegateで
    // シーンが非アクティブになる時に呼ばれます
    func sceneWillResignActive(_ scene: UIScene) {
        if let navController = window!.rootViewController as? UINavigationController {
            if let detailViewController = navController.viewControllers.last as? DetailViewController {
                // Fetch the user activity from our detail view controller so restore for later.
                scene.userActivity = detailViewController.detailUserActivity
            }
        }
    }
```

いずれも`scene`オブジェクトの`userActivity`プロパティに任意のタイミングでユーザーアクティビティを入れておきます。入れておいたユーザーアクティビティは、この後以下のデリゲートを介してシステム側に渡されるという寸法です。

```Swift
    func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
        return scene.userActivity
    }
```

**以上、[まとめ](#まとめ) に戻る**