I'm trying to implement UI state restoration in an old Objective-C UIKit app that does not use scenes and also does not have a storyboard. I have implemented the correct AppDelegate methods -- application:shouldSaveSecureApplicationState:
and application:shouldRestoreSecureApplicationState:
-- and I can see that they are being called when expected.
I have also implemented the state restoration and UIViewControllerRestoration
viewControllerWithRestorationIdentifierPath:coder:
methods for the view controllers I want to persist and restore; along with correctly setting their restorationIdentifier
and restorationClass
properties. I can also see that those are being called when expected.
I've also installed the restorationArchiveTool and have verified that the persisted archive contains the expected view controllers and state.
However, once state restoration is complete, the window's rootViewController
property is still nil and has not been assigned to the view controller that has the restorationIdentifier
of the rootViewController
at the time the state is persisted.
I found this sample app that does what I want to do -- persist and restore the state of the view controllers without the use of storyboards. Running that I verified that it does in fact work as expected. https://github.com/darrarski/iOS-State-Restoration
The difference between what that app does and what mine does, is that it always creates the top level view controllers in application:willFinishLaunchingWithOptions:.
func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
let viewControllerA = DemoViewController()
viewControllerA.title = "A"
let navigationControllerA = UINavigationController(rootViewController: viewControllerA)
navigationControllerA.restorationIdentifier = "Navigation"
let viewControllerB = DemoViewController()
viewControllerB.title = "B"
let navigationControllerB = UINavigationController(rootViewController: viewControllerB)
navigationControllerB.restorationIdentifier = "Navigation"
let tabBarController = UITabBarController()
tabBarController.restorationIdentifier = "MainTabBar"
tabBarController.viewControllers = [navigationControllerA, navigationControllerB]
window = UIWindow(frame: UIScreen.main.bounds)
window?.restorationIdentifier = "MainWindow"
window?.rootViewController = tabBarController
return true
}
Unfortunately, my app is more dynamic and the rootViewController
is determined after launch. When I updated the app to be structured more like mine, I see that it fails the same as mine and no longer restores the rootViewController.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var didRestoreState = false
func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.restorationIdentifier = "MainWindow"
return true
}
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if !didRestoreState {
let viewControllerA = DemoViewController()
viewControllerA.title = "A"
let navigationControllerA = UINavigationController(rootViewController: viewControllerA)
navigationControllerA.restorationIdentifier = "Navigation"
let viewControllerB = DemoViewController()
viewControllerB.title = "B"
let navigationControllerB = UINavigationController(rootViewController: viewControllerB)
navigationControllerB.restorationIdentifier = "Navigation"
let tabBarController = UITabBarController()
tabBarController.restorationIdentifier = "MainTabBar"
tabBarController.viewControllers = [navigationControllerA, navigationControllerB]
window?.rootViewController = tabBarController
}
window?.makeKeyAndVisible()
return true
}
func application(_ application: UIApplication, shouldSaveSecureApplicationState coder: NSCoder) -> Bool {
let libraryDirectory = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first
let appStateUrl = libraryDirectory?.appendingPathComponent("Saved Application State")
NSLog("Restoration files: \(appStateUrl?.path ?? "none")")
return true
}
func application(_ application: UIApplication, shouldRestoreSecureApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, didDecodeRestorableStateWith coder: NSCoder) {
didRestoreState = true
}
}
I don't really understand why this doesn't work since the archive file looks identical in both cases, and the methods used to create and restore state of the view controllers are being called in both cases.
Is there something else I need to do to correctly restore the view controllers without having to create them all before state restoration begins?