When your app is in the background iOS will terminate your app when it needs to reclaim resources to free up memory. In case your app supports multiple scenes the system can also decide to disconnect scenes that are in the background. In both scenarios, users will expect to be able to continue from where they left off. This is especially true for multi-window apps as a snapshot of the scene will still be visible in the app switcher.
To let the user continue using the app as if it wasn’t terminated we need to implement state restoration, which means restoring the state and the user interface after the user relaunches the app or opens an archived scene. With iOS 13 Apple introduced a new form of state restoration which is based on NSUserActivity
. In this blogpost, we will explore how this works. Let’s start by having a look at NSUserActivity
.
What is a NSUserActivity
?
You can think of NSUserActivity
as an object that contains the state of your app at a certain moment in time. Storing state in a user activity allows you to use that information to restore the app state and user interface when needed. Besides being used for state restoration NSUserActivity
is also used to support handoff, which lets the user begin an activity on one device and continue it on another.
State restoration with NSUserActivity
In a scene based app state restoration is handled per scene. This means the scene should provide a user activity containing all the data needed to restore its state and user interface. From there on the system will take care of most things for you. It will archive and persist the activity on disk, provide it to the scene delegate when the scene gets reconnected and remove if from disk when the scene gets discarded, etc.
Opt into user activity based state restoration
You can opt into user activity based state restoration by implementing the stateRestorationActivity(for:)
method in your scene delegate. In a simple implementation you can just return the user activity that is stored in the scene.
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
return scene.userActivity
}
When a scene is activated after it was disconnected the system will call the scene(_:willConnectTo:options:)
method on which you set up the window and the user interface. The UIScene.ConnectionOptions
object that is provided through this method contains data containing information about why this scene was created. It is through this object that the system hands you the user activity object it persisted earlier which you need to use to restore the state of the scene.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Setup the window (this happens automatically if you use storyboards).
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
window?.rootViewController = AppRootViewController()
if let userActivity = connectionOptions.userActivities.first ?? scene.session.stateRestorationActivity {
// Restore the user interface from the state restoration activity.
setupScene(with userActivity: userActivity)
}
window?.makeKeyAndVisible()
}
Creating a user activity
Now that we opted into user activity based state restoration and use the user activity to set up the scene when it gets connected we need to have a look at how to create and populate the scenes state restoration user activity. You create a user activity by initializing it with an activity type. It is important to know that you need to declare all the activity types that your app supports in the Info.plist
. You do this by adding the NSUserActivityTypes
key together with an array of strings containing the reverse-DNS activity types to the plist. When you have done that you can create a user activity as follows:
let userActivity = NSUserActivity(activityType: "com.app.activityType")
userActivity.title = "Main scene restoration activity"
After that, you need to store relevant data in the activity which you do by adding entries to the activities userInfo
dictionary. You do this by invoking the addUserInfoEntries(from:)
method on the activity.
What to store in the user activity
When deciding what to store in the user activity keep the following rule of thumb in mind: Don’t store everything, instead determine what is key information needed to restore the scene. For example, don’t store a full model object in the activity but store the identifier with which you can retrieve it (from a web service or database) whenever you need it.
When to create a new user activityType
A good time to create a user activity is right before the scene will go to the inactive state and sceneWillResignActive:
is called. This way we make sure that our scenes user activity object represents the last thing the user was doing before the scene went to the inactive state. In the implementation of this method, you need to go over the view hierarchy of the scene and create the appropriate activity object that can restore it. After that you let the scene know of the user activity as follows;
scene.userActivity = // Your user activity.
Conclusion
iOS 13 introduced a per scene based state restoration mechanism that makes use of NSUserActivity
to keep track of the state of your app. With this approach, you don’t encode your view hierarchy but instead, you store the state needed to rebuild the view hierarchy in the activity object.
After reading this blogpost you are now familiar with the basics of this new state restoration mechanism. Now go and try it out in your app.
Contact me on Twitter @kairadiagne if you have any questions, comments or feedback.