iosuistoryboardios9uikit-state-preservation

State Restoration in Tab Bar and Navigation Controller App


Goal: I'm trying to restore state on a tab controller-based app (with navigation controllers on each tab).

Problem: On relaunch, the selected tab seems to be restored as expected, but the navigation hierarchy inside said tab is not.

Development:

  1. I first started with the project template "Tab based app".
  2. Next, I added restoration IDs to both child view controllers and the tab bar controller.
  3. In the app delegate, I implemented application(_:shouldSaveApplicationState:) and application(_:shouldRestoreApplicationState:).

I run then app, switch to the second (right) tab, hit home, terminate. o relaunch, the right tab is displayed (as expected). So far so good.

  1. Next, I go to the storyboard and embed both child view controllers in respective navigation controllers, and assign restoration IDs to those too.

I run the app, and restoration still works. Still good.

  1. Next, I add a "detail" view controller; its class is a custom subclass of UIViewController to the storyboard, with properties to configure the contents of a debug label and its view's background color.

  2. I placed a "Show Detail..." button on each of the tabs' top view controllers, and create a segue from each into the (shared) detail view controller. So now my storyboard looks like a hexagon (also, both segues have identifiers set in Interface Builder). So, both left and right top view controllers share the same type of "detail" view controller. On show, it is configured so as to distinguish from where it has been pushed (see next point).

  3. On each of the top view controllers' prepareForSegue(_:sender:) method, I configure the pushed detail view controller differently: Different text and background color ("left" and blue, and "right" and red, respectively).

  4. I added code to the detail view controller to save and restore the state of the text and background color properties: encodeRestorableStateWithCoder(_:) and decodeRestorableStateWithCoder(_:). Also, I implemented viewDidLoad() so as to reflect those properties' values in the view. Whenever it is instantiated and pushed into the navigation through a segue, the properties are first set and then used to configre the view in viewDidLoad(). Whenever it is instantiated during restoration, the properties are set in decodeRestorableStateWithCoder(_:) and similarly used in viewDidLoad().

...but when I run this code, the last selected tab is restored but only up to the top view controller -left or right-, not the detail. Interestingly, the background color last set to the detail view controller flashes for an instant.

I placed break points in encodeRestorableStateWithCoder(_:) and decodeRestorableStateWithCoder(_:), but only the first of them is executed (encode).

  1. Wondering what might be missing, I went ahead and implemented the app delegate's application(_:viewControllerWithRestorationIdentifierPath:coder:)(returning always nil, but logging the path components passed).

The documentation is not very clear on whether this method is needed or not, and in any case all view controllers except the detail seem to be restored perfectly even without it. I added code to instantiate each view controller based on the last path component (i.e., that controller's restoration ID) and returning it.

Now, the decodeRestorableStateWithCoder(_:) is called, but the navigation still goes back to the tab's top view controller after a split second.

So, what is going on? What am I missing to implement State Preservation and Restoration in a Tab Bar + Navigation Controller app?


Solution

  • FIXED: So, there were several problems with my code...

    1. It turns out that in my case, I do not need to implement application(_:viewControllerWithRestorationIdentifierPath:coder:). (see the comments of this answer)

    2. My implementations of encodeRestorableStateWithCoder(_:) and decodeRestorableStateWithCoder(_:) were not calling super (as suggested in the accepted answer to the question above).

    3. finally, I got the right view controller (detail) to appear, but its subviews' state (text label contents and main view background color) were in the initial, empty state (not being restored to their last state -i.e., text label contents and bg color). As mentioned in this question, the viewDidLoad() is not called right after decodeRestorableStateWithCoder(_:) (like I assumed), so instead I call a common method from both viewDidLoad() and decodeRestorableStateWithCoder(_:) to update the UI.

    As usual, I rushed to post a question before searching or trying enough modifications in my code (my apologies...).

    I hope this at least helps someone else.

    As usual, I'll wait a couple of days before accepting my own answer, in case somebody sheds additional light.