Why is .stateRestorationActivity always nil in willConnectTo: ?

I'm trying to implement scene-based state restoration in an iPad OS 13 / macCatalyst app.


The docs say .stateRestorationActivity might be nil in scene(_:, willConnectTo:, options:) if data protection is turned on.


I don't have data protection turned on in my macCatalyst app, but .stateRestorationActivity is always nil, despite my verifying that stateRestorationActivity(for scene: UIScene) -> NSUserActivity? is returning a non-nil activity with an identifier that's set up in my Info.plist and has the target content identifier:

class DocumentViewContoller : UIViewController {

...

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  updateDocumentUrlInTitleBar()
  let activity = NSUserActivity.newShowActivity()     //extension creates this with correct activityType string
  activity.targetContentIdentifier = document?.fileURL.absoluteString
  activity.title = document?.fileURL.lastPathComponent
  view.window?.windowScene?.userActivity = activity
  guard isViewLoaded
  ,let activationConditions = view.window?.windowScene?.activationConditions
  else { return }
  guard let url = document?.fileURL else {
  activationConditions.canActivateForTargetContentIdentifierPredicate = NSPredicate(value: true)
  activationConditions.prefersToActivateForTargetContentIdentifierPredicate = NSPredicate(value: false)
  return }

  activationConditions.canActivateForTargetContentIdentifierPredicate = NSPredicate(format: "self == '\(url.absoluteString)'")
  activationConditions.prefersToActivateForTargetContentIdentifierPredicate = NSPredicate(format: "self == '\(url.absoluteString)'")
// activity.becomeCurrent()
  }

and I verified that the predicates were set on the scene in stateRestorationActivity(for scene:)


What other conditions do I have to meet in order that my scenes are restored when the app launches again?


If it will always be nil, why did the WWDC presenters, in 3 separate talks, tell us we should look for it in that method?

If it will always be nil in this method, during which delegate callback method should I expect it to be non-nil?

It it will always be nil in all delegate callbacks, then how do I get an app to re-open document scenes which were open when the app was last quit?

Replies

I tried printing out the discarded session in

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>)


and I was able to verify that the scene session associated with my open document before I quit the app was discarded moments after the app re-launches, and that the new scene it creates when the app opens is not the one that was open before.

I'm seeing the same issue. The UISceneDelegate based stateRestoration works correctly when run on iOS 13, but the same code run on macCatalyst behaves differently.



I looked at the session.stateRestorationActivity in scene(_:willConnectTo:options:) and in the iOS 13 case the userActivity.userInfo contains the correct information. However in macCatalyst session.stateRestorationActivity is nil.



On macCatalyst (but not iOS 13) the application(_:didDiscardSceneSessions:) is called shortly after the scene(_:willConnectTo:options:) is called and the set contain only one session and when I inspect the stateRestorationActivity it is the correct one that was saved in the previous run of the app.



It is clear that the userActivity data is available to the new run of the app, but it is not being used for some reason. I've tried the Apple supplied sample code and I get the same behavior. It seems that some environmental condition is wrong, but I have no idea what it might be.


As a side note, I've noticed that initial window sizing is not maintained if I have the "multiple windows" plist entry turned on but if I turn that off the initial window size is preserved (no effect on the stateRestorationActivity though).



Hopefully someone has managed to get this to work and can shed some light on the subject.

I enabled the restoration debugging keys:


UserDefaults.standard.set(true, forKey: "UIStateRestorationDebugLogging")
UserDefaults.standard.set(true, forKey: "UIStateRestorationDeveloperMode")


Then I ran the app under both macCatalyst and iPadOS 13. The process was the same up to when +[_UICanvasUserActivityManager _knownSceneSessionMap] returned. Then the iPad app proceeded to call -[_UICanvasUserActivityManager initWithScene:] with the discovered identfier.


However, in the macCatalyst case -[_UICanvasUserActivityManager initWithScene:] is not called but instead calls:


+[_UICanvasUserActivityManager _updatePersistedSceneSession:]_block_invoke: Adding SceneSession for persistent identifier


with a completely different identifier to the one returned from _knownSceneSessionMap and ultimately calls -[_UICanvasUserActivityManager initWithScene:] with this new identifier. Prior to this point that identifer is not reported in the debug log.


Hopefully that provides a clue for someone smarter than me :-)

While sorting through this issue, I realized that if you have the System Preference "General -> Close Windows when quitting app" set "On" that the system removes the previously saved sessions and creates a new session when starting the app. Of course, this makes sense for a document based app (and probably many others). However, it is a change from the older state restoration mechanism where the information about the view controller hierarchy was available regardless of this System Preference. It is easy enough to have your own mechanism for saving state that can be used in the case that the previous session is removed for apps that it makes sense for.


Hopefully that helps anyone that comes across this thread.


Cheers,


Greg

"I was not familiar with this "Close windows when quitting an app" setting, but mine is unchecked.


I noticed you said "if you have the System Preference "General -> Close Windows when quitting app" set "On" that the system removes the previously saved sessions and creates a new session when starting the app".


But you did not say "if you have the System Preference "General -> Close Windows when quitting app" set "off" that the system correctly restores the scenes just like on ios when starting the app". Does it work correctly for you when turn it off? Perhaps I just need to turn mine one and off again.


If apple wants to give the user control over whether the documents are automatically re-opened when the user restarts the app, I don't have a problem with that. But I do have a problem with two things: 1) no option to restore documents when re opening an app, and 2) re-opening the blank windows from previously opened documents and never allowing my code to populate those windows with the document contents.


I filed a tech support incident over this, giving up and asking dts to fix my app to handle this correctly, because I was tired of delaying features. I sent them my entire code base. They gave up pretty quickly and told me to file a bug against the OS. I have not received any word about the bug.


It looks like Catalina's MacCatalyst simply doesn't fully support document-based apps, and that fact just isn't documented anywhere. Or else it's just another huge ios 13/ctaalina bug they haven't got around to fixing yet. At this point, I've competely given up on their fixing it in catalina and I'm just trying to make enough noise for them to fix it in mac os 10.16, because we only have 5 more months to fix all the bugs in that before my app is delayed for yet a third year due to the os not supporting features they've announced.

I'm observing the same behavior in my App.

I return non-nil restoration activities for three windows. When reopening the app it reopens three windows, but they all end up defaulted because there's no restoration activity when reconnecting the scenes. (Catalyst only of course. This works on iPadOS and iOS)

Kind of a bummer, and leads to some confusing re-open activity when the app relaunches.
This also fails in Apple's sample project about state restoration...
https://developer.apple.com/documentation/uikit/uiviewcontroller/restoring_your_app_s_state?language=objc
Spoke with an engineer during a WWDC2020 Lab about this. My issue, as odd as it seems, was restarting the application too soon after quitting it.

iOS applications running on Catalyst will linger after being quit. This behavior can be observed in Activity Monitor after performing right-click-> quit on the App's icon. It was explained that this is the period where the app can finish any registered background tasks.

After waiting for my application to disappear from ActivityMonitor and then relaunching, it restored the activity provided in stateRestorationActivity(for scene: UIScene).