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

And*_*ndy 18 ios ios6 state-restoration

我一直在四处寻找状态恢复.在下面的代码中,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];
}
Run Code Online (Sandbox Code Playgroud)

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];
}
Run Code Online (Sandbox Code Playgroud)

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";
}
Run Code Online (Sandbox Code Playgroud)

小智 18

因为您要在代码中创建详细视图控制器,而不是从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;
}
Run Code Online (Sandbox Code Playgroud)

然后,您需要在还原类中实现此方法(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];
}
Run Code Online (Sandbox Code Playgroud)

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

将示例扩展为处理状态:

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

@property (nonatomic, strong) NSString *selectedRecordId;
Run Code Online (Sandbox Code Playgroud)

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

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

为了使示例有效,我在最初从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];
}
Run Code Online (Sandbox Code Playgroud)

另一个缺失的部分是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];
}
Run Code Online (Sandbox Code Playgroud)

现在这实际上还没有完成.这是因为在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;

}
Run Code Online (Sandbox Code Playgroud)

而已.

其他几点说明......

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

-(BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
    return YES;
}
- (BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder 
{
    return YES;
}
Run Code Online (Sandbox Code Playgroud)

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

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