See also #558 (persistence for Table views).
- The persistence API will allow to cleanup our code.
- with our own persistence implementation the program flow at startup was super fragile, and the user interface looked ugly, at every migration to a different API version.
- our persistence implementation was deactivated in svn3961
Diff:
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW1
A few remarks:
1) With our legacy handmade preservation/ restoration solution, each view controller on the navigation stack has the responsibility to remember its siblings above itself on the stack, and to pull them in during the restoration phase.
Note: this is the semantics of a containment (drill down into details), and from perspective of the model this seems not so wrong. However, this solution results in a weird coupling of the controllers.
A much better design is to give the navigation controller in its role of owner of the navigation stack the full responsibility to manage the stack, including the preservation and restoration.
Of course this will need a bit of restructuring and cleanup of the collaboration of our controllers.
2) Unfortunately, the base class UINavigationController does not (not yet) have the capability to preserve and restore its navigation stack. This seems to be supported only for storyboard-based applications.
This class even has a method initWithCoder, but the method seems to be inherited from its base class UIViewController, and not overridden.
I guess we need to implement it on our own.
3) By the way, all controllers that participate actively in the restoration procedure should provide a method initWithCoder. And actually, not only the controllers.
4) Our StartupViewController should not be the root controller of the navigation stack.
We want the following behavior on start of our application:
- the user should see the screen where they have left so they can seamlessly resume their work
- but the startup (with calculation of the newest test/drill tasks) should prepend this.
This is best expressed with the startup view as modal view on top (in front of) of the other views, and the db list view as root of the navigation stack.
By the way, this corresponds to our initial design many years ago before we supported rotation of views. The reasons behind changing this original design were that we saw ugly and corrupted animations and screen glitches on startup.
My current understanding of the preservation/restoration API:
Preservation:
1) iOS captures the "vital state" of an application during its termination. The resulting "object graph" has captured all "vital objects" with their state and their relationships. The idea is to have the vital state as small as possible.
2) the application must opt-in to this mechanism. To opt in, the application delegate must implement both methods (application:shouldSaveApplicationState:) and (application:shouldRestoreApplicationState:), and return YES.
Note: The opt-in pattern helps to maintain backwards-compatibility of iOS versions, and prevents applications that are not aware of this mechanism from breaking when they are suddenly confronted with a newer iOS version.
Note: these methods are deprecated since iOS 13.2, and should be replaced by their secure counterparts (shouldSaveSecureApplicationState / shouldRestoreSecureApplicationState, available since 13.2).
3) UIKit is responsible for the graph of the UIViewControllers and their relationships (child view controllers, presented or embedded).
Note: setting up correctly the relationships of a view controller hierarchy requires detailed knowledge of API internals, and these details may even change with evolution of the API.
4) UIKit walks the hierarchy, starting with the root (UIWindow.rootViewController), and gives each involved vital view controller the opportunity to encode its own vital state (encodeRestorableStateWithCoder:).
5) precondition for a view controller to be considered as vital and to take part in state preservation is a unique restorationIdentifier.
Note: UIKit uses this identifier to encode the relationships of the object graph.
Note: restorationIdentifier=nil is the mechanism for a specific object to opt-out of state preservation, even when its class supports state preservation.
6) a non-vital view controller also excludes its "children" in the hierarchy (but not its siblings) from being triggered for state preservation.
7) a triggered view controller should invoke [super encodeRestorableStateWithCoder:] at the begin of its method implementation, to allow also its superclass to participate.
See also:
UI Preservation and Restoration: http://developer.apple.com/documentation/uikit/view_controllers/preserving_your_app_s_ui_across_launches
UI Preservation: http://developer.apple.com/documentation/uikit/view_controllers/preserving_your_app_s_ui_across_launches/about_the_ui_preservation_process
encodeRestorableStateWithCoder: http://developer.apple.com/documentation/uikit/uiviewcontroller/1621461-encoderestorablestatewithcoder?language=occ
restorationIdentifier: http://developer.apple.com/documentation/uikit/uiviewcontroller/1621499-restorationidentifier
Last edit: Christa Runge 2020-07-04
Restoration:
State restoration is performed during application startup, even before the user interface is visible.
The captured object graph is not a simple tree, but usually contains plenty of cyclic pathes.
For this reason the state restoration is performed in two phases:
a) UIKit collects the references to all involved objects. If not before, they are brought now to life (created or loaded from a nib file).
b) all involved objects are triggered for restoration. At this time it is guaranteed that all involved objects are alive.
Restoration Phase 1
1) In this phase, UIKit obtains the references to all vital view controllers that will participate in state restoration.
2) Again, the application delegate must opt-in to this mechanism, and return YES to application:shouldRestoreApplicationState:.
3) UIKit then follows the graph of the packages with the vital stored state to obtain the references. Objects without vital stored state are ignored.
4) UIKit follows a 4-step-strategy to collect the references; see details in the documentation. If you are not using a storyboard, the easiest way is to setup a restorationClass and to implement viewControllerWithRestorationIdentifierPath in this class. Note that the restoration class must conform to protocol UIViewControllerRestoration.
5) You can also call that method on your other view controllers if you need references to them (e.g. if you need them as root controller).
See also:
UI Restoration:
http://developer.apple.com/documentation/uikit/view_controllers/preserving_your_app_s_ui_across_launches/about_the_ui_restoration_process
restorationClass: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621472-restorationclass
UIViewControllerRestoration: https://developer.apple.com/documentation/uikit/uiviewcontrollerrestoration
viewControllerWithRestorationIdentifierPath: https://developer.apple.com/documentation/uikit/uiviewcontrollerrestoration/1616859-viewcontrollerwithrestorationide?language=occ
Last edit: Christa Runge 2020-07-04
Reduced prio. We are not yet ready for this. Migrating to the persistence API will need major refactorings as preparations.
Still we need to re-activate our own persistence implementation
Last edit: Christa Runge 2020-07-11
See also:
http://aplus.rs/2013/state-restoration-in-ios-6-without-storyboards/
Very detailed, step-by-step instructions. Easy to follow. Highly recommended.
http://aplus.rs/2013/state-restoration-for-modal-view-controllers/
Tips and tricks for modal view controllers.
Last edit: Christa Runge 2020-08-12
TODO: DBNavigationController.completeStartup pops the remaining vc from its stack if one of them reports a failure.
This is currently implemented but not yet tested.
TODO: karatasiAppDelegate.terminateApplication triggers the vc before closing the db. Do not forget to remember the card for test and drill here.
Last edit: Christa Runge 2020-07-26
The API seems broken for modal view controllers. The controllers on the navigation stack are shown immediately, without animation; but the modal view controllers are always animated. And maximum two can be stacked before I get the error message "Warning: Attempt to present <uinavigationcontroller: 0x7ff335829c00=""> on <uinavigationcontroller: 0x7ff33582e000=""> whose view is not in the window hierarchy!"
Note: we stack 3 modal views, e.g. Drill / Edit / Learning data. In this case it does not restore the last modal view.
Note: Startup view controller is another modal view. But actually, startup should process in the background, and be visible only if it takes excessive time.</uinavigationcontroller:></uinavigationcontroller:>
Last edit: Christa Runge 2020-08-17