Mastering State Restoration in iOS: Seamless User Experiences
State Restoration is a powerful UIKit feature that allows your iOS application to return to its previous state after being terminated by the system or the user. This capability significantly improves the user experience by eliminating the need to re-enter data or navigate back to a specific view. Implementing state restoration is crucial for building robust and user-friendly iOS applications.

What is State Restoration and Why is it Important?
State Restoration is a mechanism provided by UIKit that enables your application to save its current state before being terminated and then restore that state when it is relaunched. This is especially important for long-running tasks, complex navigation flows, or when a user's device runs low on memory, forcing the OS to terminate background apps. Without state restoration, users might lose their progress and become frustrated, leading to a poor app experience.
Imagine a user filling out a multi-step form or navigating deep within a photo gallery. If the app is terminated and then relaunched, they would expect to find themselves right where they left off. State restoration makes this possible by saving information about your app's view controllers, their properties, and their positions in the navigation stack.
Key benefits of implementing state restoration include:
- Enhanced User Experience: Users perceive the app as more reliable and robust.
- Reduced Frustration: Users don't have to restart tasks or re-enter data.
- Improved Engagement: A seamless experience encourages continued app usage.
- Compliance with iOS Best Practices: Apple strongly recommends implementing state restoration for apps that deal with user-generated content or complex workflows.
It's important to note that state restoration is not a substitute for saving user data. You should still persist critical user data (like documents or settings) using UserDefaults, Core Data, or property list files. State restoration focuses on the UI state – what the user was looking at and interacting with.
Enabling State Restoration in Your AppDelegate
To begin using state restoration, you must inform UIKit that your application supports it. This is done by implementing specific methods in your AppDelegate (for UIKit apps) or SceneDelegate (for SwiftUI apps with UIKit view controller hierarchies). For UIKit-based applications, you'll primarily work with AppDelegate methods.
First, you need to tell UIKit whether your application should attempt to restore state when it launches. You achieve this by implementing the application(_:shouldSaveApplicationState:) and application(_:shouldRestoreApplicationState:) methods. Typically, you'll return true from both to enable the feature.
Here's how you can enable state restoration in your AppDelegate:
Adopting Restoration Identifiers for View Controllers
The core of state restoration relies on restorationIdentifier. Every view controller that you want to participate in state restoration must have a unique restorationIdentifier. This identifier acts like a key, allowing UIKit to locate and instantiate the correct view controller during restoration.
When UIKit saves your app's state, it traverses your view controller hierarchy, starting from the root view controller. For each view controller with a restorationIdentifier, UIKit saves its class name and identifier. When restoring, UIKit uses these identifiers to either find an existing instance of the view controller or create a new one.
You can set the restorationIdentifier in Interface Builder or programmatically. It's crucial that these identifiers are unique within their respective restoration paths.
Consider a ViewController that displays a list of items and a DetailViewController that shows the details of a selected item. Both should have restorationIdentifiers.
Encoding and Decoding State with UIStateRestoring
While restorationIdentifier tells UIKit which view controller to restore, the encodeRestorableState(with:) and decodeRestorableState(with:) methods (part of the UIStateRestoring protocol) are where you save and load the specific data that defines that view controller's state. These methods are called automatically by UIKit when saving and restoring.
In encodeRestorableState(with:), you'll write any crucial properties of your view controller to the provided NSCoder object. This could include selected indices, text field contents, scroll positions, or any other data necessary to reconstruct the current UI.
In decodeRestorableState(with:), you'll read those properties back from the NSCoder. It's important to not only read the data but also to use it to update your view controller's UI accordingly. Remember to call the super implementation of these methods to allow parent view controllers to save/restore their state.
Let's enhance our DetailViewController to save and restore its itemID property:
Restoring the View Controller Hierarchy
When UIKit needs to restore your app's view controller hierarchy, it follows a specific process that you can influence. For view controllers pushed onto a UINavigationController or presented modally, UIKit can often recreate the stack automatically if they have restorationIdentifiers.
However, for more complex scenarios, particularly when a view controller is instantiated programmatically and requires custom initialization (like passing data during init), you need to provide a restoration class. This is done by implementing application(_:viewControllerWithRestorationIdentifierPath:coder:) in your AppDelegate.
This method is UIKit's chance to ask you to provide an instance of a view controller based on its restorationIdentifierPath and the encoded data. If you return nil, UIKit will try to instantiate the view controller from a storyboard (if available) or by calling its init(coder:) method.
For DetailViewController, which might receive an itemID during its setup, you might implement this to ensure it's properly configured upon restoration:
Note: This method is compatible with iOS 6.0 and later.
Disabling Restoration for Individual Views or Controllers
There might be cases where you do not want certain view controllers or even specific views to participate in state restoration. For instance, temporary alert controllers, onboarding flows, or view controllers that display highly transient data might not need to be restored.
To prevent a view controller from being restored, simply do not set its restorationIdentifier. UIKit will skip any view controllers without this identifier during the state-saving process.
For elements within a view controller that are part of the view hierarchy but don't need their state restored, you can set their restorationIdentifier to nil or simply not assign one. This is generally more relevant for custom views that might have subviews you manage directly.
Consider an ephemeral TermsAndConditionsViewController that should never be restored:
By carefully choosing which parts of your application participate in state restoration, you can optimize performance and ensure a logical user experience.
Best Practices and Considerations
Implementing state restoration effectively requires attention to detail and adherence to best practices:
- Unique Restoration Identifiers: Ensure
restorationIdentifiers are unique within their restoration paths. Duplicates can lead to unpredictable behavior. - Minimal Data Storage: Only encode and decode the absolute minimum data required to recreate the UI state. Avoid saving large data objects or images, which can slow down the process.
- Handle
init(coder:): If your view controllers are instantiated from storyboards, they will useinit(coder:). Ensure your custom initializers handleNSCoderproperly or that any necessary setup is done inviewDidLoad(which is called afterdecodeRestorableState). - Test Thoroughly: Test state restoration under various conditions. Force quit the app from the app switcher, reboot the device, and try different navigation depths.
- Version Compatibility: Ensure your
AppDelegatemethods andUIStateRestoringimplementations are compatible with your deployment target. State restoration is available from iOS 6.0. - Navigation Controllers and Tab Bar Controllers: UIKit typically handles the restoration of the stack for these container view controllers if their child view controllers have
restorationIdentifiers. prepare(for:sender:)vs. Restoration: Remember that is for segues, which might not be involved in a restored app state. Your method should handle data passing specifically for restoration.
By following these guidelines, you can build a robust and seamless state restoration experience for your users, making your app feel more professional and reliable.
Common Interview Questions
What's the difference between State Restoration and saving data with `UserDefaults`?
State restoration focuses on preserving the user interface's current state and navigation stack (e.g., 'What screen was the user on? What text was typed into this field?'). `UserDefaults`, on the other hand, is for persisting user preferences, settings, or small pieces of application-specific data that should remain consistent across app launches and device reboots, regardless of the UI's last state.
Does State Restoration work automatically with SwiftUI?
State restoration, as described with `UIStateRestoring` and `restorationIdentifier`, is primarily a UIKit feature. While SwiftUI apps running on iOS 13+ use `UISceneDelegate` (which has similar restoration methods), direct state restoration of SwiftUI `View` hierarchies isn't built-in in the same way. You often need to bridge between UIKit view controllers within your SwiftUI app (e.g., using `UIViewControllerRepresentable`) or manually manage state persistence within SwiftUI using `@AppStorage`, `SceneStorage`, or custom persistence solutions for a similar effect.
What happens if a view controller's class changes or is removed after state has been saved?
If a stored `restorationIdentifier` corresponds to a view controller class that no longer exists or has been renamed, UIKit will likely fail to restore that specific view controller. It will skip it and often continue attempting to restore other parts of the hierarchy. It's crucial to handle these scenarios gracefully, potentially by providing fallback mechanisms or simply accepting that those components won't be restored.