Восстановление состояния 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";
}
3 ответа

Поскольку вы создаете свой контроллер подробных представлений в коде, а не создаете его из раскадровки, вам нужно реализовать класс восстановления, поэтому процесс восстановления системы знает, как создать контроллер подробных представлений.

Класс восстановления - это просто factory, который знает, как создать определенный контроллер просмотра с пути восстановления. На самом деле вам не нужно создавать отдельный класс для этого, мы можем просто обработать его в MyOtherViewController:

в MyOtherViewController.h, выполните протокол: UIViewControllerRestoration

Затем, когда вы устанавливаете идентификатор восстановления в MyOtherViewController.m, также задайте класс восстановления:

- (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];
}

Это доставляет вам до тех пор, пока подсистема восстановления может создавать и нажимать контроллер Detail View на стек навигационной системы. (Что вы изначально задали.)

Расширение примера для обработки состояния:

В реальном приложении вам нужно будет сохранить состояние и обработать его восстановление... Я добавил следующее определение свойства MyOtherViewController для имитации этого:

@property (nonatomic, strong) NSString *selectedRecordId;

И я также изменил метод MyDotherViewContoller viewDidLoad, чтобы установить заголовок Detail на любой идентификатор записи, выбранный пользователем:

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

Чтобы сделать этот пример работы, я устанавливаю выбранныйRecordId, когда сначала нажимает Detail View из 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, ваше подробное представление, чтобы сохранить состояние контроллера детали.

-(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];
}

Теперь это на самом деле еще не совсем работает. Это связано с тем, что метод "ViewDidLoad" вызывается при восстановлении до метода decoreRestorablStateWithCoder. Следовательно, заголовок не будет установлен до отображения представления. Чтобы исправить это, мы обрабатываем либо задание заголовка для контроллера вида Detail в viewWillAppear:, либо мы можем изменить метод viewControllerWithRestorationIdentifierPath для восстановления состояния, когда он создает класс следующим образом:

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

Что это.

Несколько других заметок...

Я предполагаю, что вы сделали следующее в своем делете приложения, хотя я не видел код?

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

Я видел немного flakiness при запуске демонстрационного кода в симуляторе, при этом он правильно сохранял смещение прокрутки. Казалось, что это время от времени для меня, но не все время. Я подозреваю, что это может быть связано с тем, что на самом деле восстановление MyTableViewController также должно использовать метод restoreClass, поскольку он создан в коде. Однако я еще не пробовал это.

Мой метод тестирования заключался в том, чтобы поместить приложение в фоновый режим, вернувшись на трамплин (требуется для сохранения состояния приложения), а затем перезапустив приложение из 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, вам нужно будет предоставить не только restorationIdentifier, но также restorationClass для контроллера подробных представлений.

Вы можете оставить его неназначенным, и в этом случае вызов -(UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder вызывается в делетете приложения для создания объекта (контроллер просмотра с restorationIdentifier, но не restorationClass).

Попробуйте следующее (обратите внимание на протокол UIViewControllerRestoration):

@interface MyViewController ()<uiviewcontrollerrestoration>
@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
</uiviewcontrollerrestoration>

Также обратите внимание, что это очень простой пример, обычно у вас может быть намного больше интересного в -viewControllerWithRestorationIdentifierPath:coder:

licensed under cc by-sa 3.0 with attribution.