Восстановление состояния 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";
}
Ответы
Ответ 1
Поскольку вы создаете свой контроллер подробных представлений в коде, а не создаете его из раскадровки, вам нужно реализовать класс восстановления, поэтому процесс восстановления системы знает, как создать контроллер подробных представлений.
Класс восстановления - это просто 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, чтобы имитировать чистый старт.
Ответ 2
Единственная проблема с вашим кодом - контроллер навигации
эти две строки
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
Ответ 3
Поскольку вы делаете это в коде, а не через 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
Также обратите внимание, что это очень простой пример, обычно у вас может быть намного больше интересного в -viewControllerWithRestorationIdentifierPath:coder: