问题描述
我一直在四处寻找状态恢复。在下面的代码中,UITableViewController的滚动位置得到恢复,但是,如果我要进入详细视图(将MyViewController的实例推送到导航堆栈),当应用程序重新启动时,它总是返回到第一个视图导航堆栈中的控制器(即MyTableViewController)。有人能帮我恢复到正确的视图控制器(即MyOtherViewController)吗?
I've been toying around with state restoration. In the code below, the scroll position of the UITableViewController gets restored, however, if I were to tap through into the detail view (pushing an instance of MyViewController onto the navigation stack), when the app restarts, it always returns to the first view controller in the navigation stack (i.e. MyTableViewController). Would somebody be able to help me restore to the correct view controller (i.e. 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实例化它,您需要实现一个恢复类,因此系统恢复过程知道如何创建详细视图控制器。
Because you are creating your detail view controller in code, and not instantiating it from a Storyboard, you need to implement a restoration class, so the system restoration process knows how to create the detail view controller.
恢复类实际上只是一个知道如何从恢复路径创建特定视图控制器的工厂。你实际上不必为此创建一个单独的类,我们可以在MyOtherViewController中处理它:
A restoration class is really just a factory which knows how to create a specific view controller from a restoration path. You don't actually have to create a separate class for this, we can just handle it in MyOtherViewController:
,实现协议:UIViewControllerRestoration
in MyOtherViewController.h, implement the protocol: UIViewControllerRestoration
然后在MyOtherViewController.m中设置restoreID时,还要设置恢复类:
Then when you set the restorationID in MyOtherViewController.m, also set the restoration class:
- (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)
You then need to implement this method in the restoration class (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];
}
这可以让您恢复子系统能够创建并推送详细信息查看导航堆栈上的控制器。 (您最初提出的问题。)
That gets you as far as the restoration subsystem being able to create and push the Detail View controller onto the Navigation Stack. (What you initially asked.)
将示例扩展为处理状态:
在一个真实的应用程序中,你需要保存状态并处理恢复它...我将以下属性定义添加到MyOtherViewController来模拟这个:
In a real app, you'd need to both save the state and handle restoring it... I added the following property definition to MyOtherViewController to simulate this:
@property (nonatomic, strong) NSString *selectedRecordId;
我还修改了MyOtherViewContoller的viewDidLoad方法,将Detail标题设置为用户选择的任何记录ID: / p>
And I also modified MyOtherViewContoller's viewDidLoad method to set the Detail title to whatever record Id the user selected:
- (void)viewDidLoad
{
[super viewDidLoad];
// self.title = @"Detail";
self.title = self.selectedRecordId;
self.view.backgroundColor = [UIColor redColor];
self.view.restorationIdentifier = @"detailView";
}
为了使示例正常工作,我在最初推送详细视图时设置了selectedRecordId MyTableViewController:
To make the example work, I set selectedRecordId when initially pushing the Detail View from MyTableViewController:
- (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中的方法,你的详细信息视图,要保存细节状态控制器。
The other missing piece are the methods in MyOtherViewController, your detail view, to save the state of Detail controller.
-(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方法以在创建类时恢复状态:
Now this actually doesn't quite work yet. This is because the "ViewDidLoad" method is called on restoration before the decoreRestorablStateWithCoder method is. Hence the title doesn't get set before the view is displayed. To fix this, we handle either set the title for the Detail view controller in viewWillAppear: , or we can modify the viewControllerWithRestorationIdentifierPath method to restore the state when it creates the class like this:
+(UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
MyOtherViewController *controller = [[self alloc] initWithNibName:nil bundle:nil];
controller.selectedRecordId = [coder decodeObjectForKey:@"selectedRecordId"];
return controller;
}
就是这样。
其他一些注释......
我认为你做了以下事情在您的App Delegate中,即使我没有看到代码?
i presume you did the following in your App Delegate even though i didn't see the code?
-(BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
return YES;
}
- (BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
return YES;
}
我在模拟器中运行演示代码时看到了一些瑕疵正确保留Scroll偏移量。它似乎偶尔为我工作,但不是所有的时间。我怀疑这可能是因为MyTableViewController的真正恢复也应该使用recoverClass方法,因为它在代码中实例化。但是我还没试过。
I saw a bit of flakiness when running the demo code in the simulator with it correctly preserving the Scroll offset. It seemed to work occasionally for me, but not all the time. I suspect this may be because really restoration of MyTableViewController should also use a restorationClass approach, since it's instantiated in code. However I haven't tried that out yet.
我的测试方法是将应用程序放回到后台,返回跳板(需要保存应用程序状态。),然后从XCode中重新启动应用程序模拟一个干净的开始。
My testing method, was to put the app in the background by returning to springboard (required for the app state to be saved.) , then relaunching the app from within XCode to simulate a clean start.
这篇关于UINavigationController状态恢复(没有故事板)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!