saving iPhone program state with a deep UINavigationController
Can someone share a good way to save the program state (UINavigationController stack, etc) of an iPhone application.
My application obtains a bunch of information from the network and I want to return the person back to the last screen they were on, even if it was 3 or 4 screens deep.
I assume that I will need to reload the data from the network along the way as I recreate the UINavigation con开发者_如何学Gotrollers. I don't necessarily have a problem with this.
I'm thinking about maybe having my UINavigationController objects implement some type of protocol which allow me to save/set their state? I'm looking to hear from others who may have needed to implement a similar scenario and how they accomplished it.
My application has a UITabbarController at the root and UINavigationController items for each tab bar item.
thanks!
Here is what I ended up doing as a solution.
I created an protocol which contains a method to "obtain state" and then to "init with state from dictionary method".
So, when the application closes, I loop through all my controllers and ask them for state. And then when application starts back up, I do init with state, passing in the dictionary I serialized. It works well!
To save and reload the navigation stack I did the following.
- (void)applicationDidBecomeActive:(UIApplication*)application{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// if a navigation stack is stored in UserDefaults
// loop through the array until all views are pushed onto the stack.
// This will bring the user to the view they were on when the app terminated.
if([prefs objectForKey:@"viewStack"] != nil){
for (id viewItem in [prefs arrayForKey:@"viewStack"]) {
[self.navigationController pushViewController:viewItem animated:NO];
}
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Create an array to store the array of viewControllers currently on the stack. NSArray *stackArray = [NSArray arrayWithArray:navigationController.viewControllers]; // if user has navigated beyond the rootview, save the nav stack. if([stackArray count] > 1) [[NSUserDefaults standardUserDefaults] setObject:stackArray forKey:@"viewStack"]; }
I did this in one of my apps and from what I can see its quite a manuagel process
For each of your TargetViewControllers you will have to push them onto the view controller, set animated to NO so it looks like it is restoring state.
[[self navigationController] pushViewController:targetViewController animated:NO];
Maybe someone else can point you to a framework that allows you to persist these view controllers.
I guess I use (sort of) the same approach as Jr. I'll post it her for educational sake.
I have a protocol that looks like this:
FSViewControllerRestoration.h
#import <Foundation/Foundation.h>
@protocol FSViewControllerStateRestoration <NSObject>
@required;
- (NSDictionary *)currentState;
- (void)restoreState:(NSDictionary *)state;
@end
I also have a class that is used for loading and storing the view controller stack. When loading the view controller stack, it checks if the class conforms to the protocol and if it does, calls the restoreState: method.
FSViewControllerStorage.h
#import <Foundation/Foundation.h>
#import "FSViewControllerStateRestoration.h"
@interface FSViewControllerStateStorage : NSObject
+ (BOOL)storeViewControllerStack:(NSArray *)stack;
+ (NSArray *)loadViewControllerStack;
@end
FSViewControllerStorage.m
#import "FSViewControllerStateStorage.h"
#define FS_PATH_APPLICATION_STATE_FILE [FS_PATH_DOCUMENTS_DIR stringByAppendingPathComponent:@"appstate.dat"]
@implementation FSViewControllerStateStorage
+ (BOOL)storeViewControllerStack:(NSArray *)stack
{
DLog(@"storing view controller stack ...");
if (stack.count <= 1)
{
return [NSKeyedArchiver archiveRootObject:nil toFile:FS_PATH_APPLICATION_STATE_FILE];
}
NSArray *items = @[];
for (UIViewController *viewController in stack)
{
NSString *className = NSStringFromClass(viewController.class);
NSDictionary *state = @{};
if ([viewController conformsToProtocol:@protocol(FSViewControllerStateRestoration)])
{
state = [(id <FSViewControllerStateRestoration>)viewController currentState];
}
items = [items arrayByAddingObject:@{@"Class" : className, @"State" : state}];
}
return [NSKeyedArchiver archiveRootObject:items toFile:FS_PATH_APPLICATION_STATE_FILE];
}
+ (NSArray *)loadViewControllerStack
{
DLog(@"loading view controller stack ...");
NSArray *items = [NSKeyedUnarchiver unarchiveObjectWithFile:FS_PATH_APPLICATION_STATE_FILE];
NSArray *stack = @[];
for (NSDictionary *dictionary in items)
{
NSString *className = [dictionary objectForKey:@"Class"];
NSDictionary *state = [dictionary objectForKey:@"State"];
Class class = NSClassFromString(className);
UIViewController *viewController = [[class alloc] init];
if ([viewController conformsToProtocol:@protocol(FSViewControllerStateRestoration)])
{
[(id <FSViewControllerStateRestoration>)viewController restoreState:state];
}
stack = [stack arrayByAddingObject:viewController];
}
return stack;
}
@end
In my AppDelegate the storage class is used as follows ...
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[FSViewControllerStateStorage storeViewControllerStack:self.navigationController.viewControllers];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSArray *viewControllers = [FSViewControllerStateStorage loadViewControllerStack];
if (viewControllers.count > 0)
{
self.navigationController.viewControllers = viewControllers;
}
}
And finally an example of storing and restoring state in a viewController that conforms to the protocol:
#pragma mark - View controller state restoration
- (NSDictionary *)currentState
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
if (_newsItem)
{
[dictionary setObject:_newsItem forKey:@"NewsItem"];
}
if (_comments)
{
[dictionary setObject:_comments forKey:@"Comments"];
}
return dictionary;
}
- (void)restoreState:(NSDictionary *)state
{
FSNewsItem *newsItem = [state objectForKey:@"NewsItem"];
if (newsItem)
{
self.newsItem = newsItem;
}
NSArray *comments = [state objectForKey:@"Comments"];
if (comments)
{
self.comments = comments;
}
}
精彩评论