UINavigationController状态恢复(没有故事板)

我一直在四处寻找状态恢复。 在下面的代码中,UITableViewController的滚动位置得到恢复,但是,如果我要进入详细视图(将MyViewController的实例推送到导航堆栈),当应用程序重新启动时,它总是返回到第一个视图导航堆栈中的控制器(即MyTableViewController)。 有人能帮我恢复到正确的视图控制器(即MyOtherViewController)吗?

AppDelegate.m

- (BOOL)launchWithOptions:(NSDictionary *)launchOptions { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. MyTableViewController *table = [[MyTableViewController alloc] initWithStyle:UITableViewStylePlain]; table.depth = 0; UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:table]; navCon.restorationIdentifier = @"navigationController"; self.window.rootViewController = navCon; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; }); return YES; } - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return [self launchWithOptions:launchOptions]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return [self launchWithOptions:launchOptions]; } 

MyTableViewController.m

 - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if(self) { self.restorationIdentifier = @"master"; } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.title = @"Master"; self.tableView.restorationIdentifier = @"masterView"; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 5; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 10; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return [NSString stringWithFormat:@"Section %d", section]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if(!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } cell.textLabel.text = [NSString stringWithFormat:@"%d", indexPath.row]; return cell; } #pragma mark - Table view delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { MyOtherViewController *vc = [[MyOtherViewController alloc] initWithNibName:nil bundle:nil]; [self.navigationController pushViewController:vc animated:YES]; } 

MyOtherViewController.m

 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { self.restorationIdentifier = @"detail"; } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.title = @"Detail"; self.view.backgroundColor = [UIColor redColor]; self.view.restorationIdentifier = @"detailView"; } 

由于您是在代码中创建详细视图控制器,而不是从Storyboard实例化,因此需要实现恢复类,因此系统还原过程知道如何创建详细视图控制器。

恢复类实际上只是一个知道如何从恢复路径创建特定视图控制器的工厂。 你实际上不必为此创建一个单独的类,我们可以在MyOtherViewController中处理它:

在MyOtherViewController.h中,实现协议:UIViewControllerRestoration

然后在MyOtherViewController.m中设置restoreID时,还要设置恢复类:

 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { self.restorationIdentifier = @"detail"; self.restorationClass = [self class]; //SET THE RESTORATION CLASS } return self; } 

然后,您需要在还原类中实现此方法(MyOtherViewController.m)

  +(UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder { //At a minimum, just create an instance of the correct class. return [[MyOtherViewController alloc] initWithNibName:nil bundle:nil]; } 

这可以让您恢复子系统能够创建并将详细视图控制器推送到导航堆栈。 (你最初问的是什么。)

将示例扩展为处理状态:

在一个真正的应用程序中,你需要保存状态并处理恢复它…我将以下属性定义添加到MyOtherViewController来模拟这个:

 @property (nonatomic, strong) NSString *selectedRecordId; 

我还修改了MyOtherViewContoller的viewDidLoad方法,将Detail标题设置为用户选择的任何记录ID:

 - (void)viewDidLoad { [super viewDidLoad]; // self.title = @"Detail"; self.title = self.selectedRecordId; self.view.backgroundColor = [UIColor redColor]; self.view.restorationIdentifier = @"detailView"; } 

为了使示例有效,我在最初从MyTableViewController推送详细信息视图时设置selectedRecordId:

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { MyOtherViewController *vc = [[MyOtherViewController alloc] initWithNibName:nil bundle:nil]; vc.selectedRecordId = [NSString stringWithFormat:@"Section:%d, Row:%d", indexPath.section, indexPath.row]; [self.navigationController pushViewController:vc animated:YES]; } 

另一个缺失的部分是MyOtherViewController中的方法,您的详细信息视图,用于保存Detail控制器的状态。

 -(void)encodeRestorableStateWithCoder:(NSCoder *)coder { [coder encodeObject:self.selectedRecordId forKey:@"selectedRecordId"]; [super encodeRestorableStateWithCoder:coder]; } -(void)decodeRestorableStateWithCoder:(NSCoder *)coder { self.selectedRecordId = [coder decodeObjectForKey:@"selectedRecordId"]; [super decodeRestorableStateWithCoder:coder]; } 

现在这实际上还没有完成。 这是因为在decoreRestorablStateWithCoder方法之前调用“ViewDidLoad”方法。 因此,在显示视图之前不会设置标题。 为了解决这个问题,我们处理在viewWillAppear:中为Detail视图控制器设置标题,或者我们可以修改viewControllerWithRestorationIdentifierPath方法以在创建类时恢复状态:

 +(UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder { MyOtherViewController *controller = [[self alloc] initWithNibName:nil bundle:nil]; controller.selectedRecordId = [coder decodeObjectForKey:@"selectedRecordId"]; return controller; } 

而已。

其他几点说明……

我假设您在App Delegate中执行了以下操作,即使我没有看到代码?

 -(BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder { return YES; } - (BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder { return YES; } 

在模拟器中运行演示代码时,我看到了一些瑕疵,它正确地保留了Scroll偏移。 它似乎偶尔为我工作,但不是所有的时间。 我怀疑这可能是因为MyTableViewController的真正恢复也应该使用recoverClass方法,因为它在代码中实例化。 但是我还没试过。

我的测试方法是将应用程序放在后台,返回跳板(需要保存应用程序状态。),然后从XCode中重新启动应用程序以模拟干净的启动。

您的代码唯一的问题是导航控制器这两行

 UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:table]; navCon.restorationIdentifier = @"navigationController"; 

我不知道为什么导航控制器永远不会得到它的恢复类。 我有同样的问题。 我发现了一些替代解决方案,可以在故事板中单独创建导航控制器并使用它。

这是代码

 UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil]; UINavigationController* navigationController = [storyboard instantiateViewControllerWithIdentifier: @"UINavigationController"]; navigationController.viewControllers = @[myController]; 

它会工作正常。

请关注此http://petr-pavlik.squarespace.com/blog/2013/6/16/stare-restoration-without-storyboard

由于您是在代码中而不是通过Storyboard执行此操作,因此您不仅需要为详细视图控制器提供restoreIdentifier,还需要还提供restorationClass

您可以将其保留为未分配的情况-(UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder在应用程序委托-(UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder用以生成对象(具有restorationIdentifier视图控制器)但没有restorationClass )。

请尝试以下(请注意UIViewControllerRestoration协议):

 @interface MyViewController () @end @implementation - (id)init { self = [super init]; if (self) { // Custom initialization } if( [self respondsToSelector:@selector(restorationIdentifier)] ){ self.restorationIdentifier = @"DetailViewController"; self.restorationClass = [self class]; } return self; } #pragma mark - State restoration + (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder { UIViewController *viewController = [[self alloc] init]; return viewController; } @end 

另请注意,这是一个非常简单的示例,通常在-viewControllerWithRestorationIdentifierPath:coder:可能会有更多有趣的事情-viewControllerWithRestorationIdentifierPath:coder: