【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.first
もsession.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.
とあり、シーンが起動された際に**NSUserActivity
のactivityType
として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.first
もsession.stateRestorationActivity
のどちらにも同じユーザーアクティビティが入ってきそうです。
確かに、次の項目で紹介するAppleのサンプルコードで検証した感じではそのようでした。
シーンのユーザーアクティビティを保存する方法
付随して、サンプルコードで見つけたユーザーアクティビティの保存に関するパターンも追記しておきます。
・パターン1
Sample Code - Supporting Multiple Windows on iPad
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
// シーンが非アクティブになる時に呼ばれます
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
}
以上、まとめ に戻る