Why doesn't my app return to my detail view when it's restored?
Asked Answered
P

3

2

My app has a simple organization, which I've configured in an Interface Builder storyboard (not in code). There is a Navigation View Controller, which has its Root View Controller set to my Main View Controller. My Main View contains a table, where cells segue to a Detail View Controller.

When I suspend the application while looking at the Detail View and then resume it, I'm returned to the Main View, rather than the Detail view. Why might this be?

Details:

I have set Restoration IDs in Interface Builder for the Navigation View Controller, the Main View Controller and the Detail View Controller. I've also tried adding a Restoration ID to the Table View and making the Main View Controller implement UIDataSourceModelAssociation.

My app is returning YES from shouldRestoreApplicationState and both the Main View and the Detail View have encode/decodeRestorableStateWithCoder methods.

I'm testing suspend/resume using the simulator: I run the app, navigate to the Detail View, hit the home button, and then click the stop button in XCode. To resume, I'm running the app again from XCode.

I see the following calls on suspend:

AppDelegate shouldSaveApplicationState
MainViewController encodeRestorableStateWithCoder
DetailViewController encodeRestorableStateWithCoder

And on resume:

AppDelegate shouldRestoreApplicationState
AppDelegate viewControllerWithRestorationIdentifierPath Navigation
AppDelegate viewControllerWithRestorationIdentifierPath Navigation/MainView
MainViewController viewDidLoad
AppDelegate viewControllerWithRestorationIdentifierPath Navigation/DetailView
MainViewController decodeRestorableStateWithCoder

In addition to the wrong view being restored, there's something else odd: Why is the Restoration Identifier Path for the Detail View "Navigation/DetailView" and not "Navigation/MainView/DetailView"? There is no direct relationship between the Navigation View Controller and the Detail View Controller. Their only connection in Interface Builder is via the segue from the Main View.

Have I misconfigured something?

I have tried assigning a Restoration Class to the Detail View. When that restoration code is invoked, it fails because the UIStateRestorationViewControllerStoryboardKey is not set in the coder.

Here's a toy version of my project which replicates the problem: https://github.com/WanderingStar/RestorationTest

I'm trying this with XCode Version 5.0 (5A1413) and iOS Simulator Version 7.0 (463.9.4), in case those are relevant.

Puritanism answered 7/10, 2013 at 6:23 Comment(0)
P
5

The answer turned out to be simple: I was not calling

[super encodeRestorableStateWithCoder:coder];

in the encodeRestorableStateWithCoder:coder method in my View Controllers (and doing the same in decode...) which is what sets the storyboard in the coder.

This tutorial helped me step through each step of the process, and find out where I'd gone wrong: http://useyourloaf.com/blog/2013/05/21/state-preservation-and-restoration.html

Also, it turns out that "Navigation/DetailView" is what's expected. The Navigation View Controller restores all of the views in its stack and then puts them back into the stack, rather than each view restoring the later views in the stack.

Puritanism answered 8/10, 2013 at 4:30 Comment(0)
I
0

In the iOS App Programming Guide, section "State Preservation and Restoration" there is a convenient checklist for what you have to do to make restoration work.

After looking at your code it seems that you forgot step 3, Assign Restoration Classes. Your classes do not have these properties, and you did not implement viewControllerWithRestorationIdentifierPath in the app delegate.

Assign restoration classes to the appropriate view controllers. (If you do not do this, your app delegate is asked to provide the corresponding view controller at restore time.) See “Restoring Your View Controllers at Launch Time.”

Impatience answered 7/10, 2013 at 7:37 Comment(5)
That page also says: "If the view controller is always loaded from your app’s main storyboard file at launch time, do not assign a restoration class. Instead, let your app delegate find the object or take advantage of UIKit’s support for implicitly finding restored objects." But that's a good idea to try. I'll see if explicitly naming the restoration classes works.Puritanism
Reading carefully the 4 point process you quote, you still have to implement viewControllerWithRestorationIdentifierPath in step 2. Points 3. and 4. might be misleading, but read on through the paragraph beneath... " UIKit stops trying to locate your view controller".Impatience
I tried adding an explicit restoration class, and it doesn't fix the problem. Following the example of listing 4-1 in the State Preservation and Restoration section, I tried to restore the Detail view using the storyboard. It turns out that the storyboard key is nil! I guess that means that Cocoa thinks that the view was not instantiated from the storyboard in the first place? That makes no sense to me, since it clearly was.Puritanism
Thanks for the suggestion. It turns out that for storyboard-based apps, you don't need to assign Restoration Classes or implement viewControllerWithRestorationIdentifierPath in either the App Delegate or in the class being restored.Puritanism
You need to implement viewControllerWithRestorationIdentifierPath in app delegate to help it find the storyboard instance when the path has changed e.g. if preserved a split view in landscape and restoring it in portrait.Inviolate
I
0

I took a look at your sample and the applicationWillFinishLaunching is missing [self.window makeKeyAndVisible] which is a requirement for state restoration. This will make the split controller immediately collapse and then it will be restored correctly.

There is an issue that if it was preserved in landscape, i.e. separated split view , and then launched in portrait then the path will not be correct. In this case at launch it will first collapse to match the current screen, then it begin restore and first separate, then after restore has finished it will collapse again to match the current screen. During this time you need to implement viewControllerWithRestorationIdentifierPath and use the last string in the path to identify the controller and return it after having captured it from what the storyboard created initially in will finish launching. Then you can clear those properties in didFinish.

Inviolate answered 8/6, 2019 at 17:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.