Help us understand the problem. What is going on with this article?

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

はじめに

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

    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.firstsession.stateRestorationActivityにも同じユーザーアクティビティが入ってきそう。

WWDC2019-212

まず、WWDC2019-212の中盤ConnectionOptions#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

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

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

APIドキュメントではどうか

まず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プロパティの説明には、

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.

とあり、シーンが起動された際にNSUserActivityactivityTypeとしてUIScene.ConnectionOptions#handoffUserActivityType のタイプ情報のみが提供されるようです。
その後、別のデリゲートメソッドにHandoffのユーザーアクティビティは渡されるとのこと。「additional methods of the delegate」が示すところを探してみると、

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

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

ちなみに、アプリケーション単位でHandoffでのユーザーアクティビティの受け渡しを行うのは application(_:continue:restorationHandler:) という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:)に記載がありました。

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には何が入るか?

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 をシステムに提供できるのは、

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

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

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

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

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

スクリーンショット 2019-10-28 14.55.54.png

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

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

・パターン1

Sample Code - Supporting Multiple Windows on iPad

特定の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
    }

・パターン2

SampleCode - Restoring Your App’s State

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プロパティに任意のタイミングでユーザーアクティビティを入れておきます。入れておいたユーザーアクティビティは、この後以下のデリゲートを介してシステム側に渡されるという寸法です。

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

残る疑問

"ConnectionOptions#userActivities" なぜ、複数形か・・。すべての資料を見られたわけではないと思うので、判明次第編集したいと思います。

以上、まとめ に戻る

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした