ios - UINavigationController State Restoration(sin guiones gráficos)
ios6 state-restoration (3)
He estado jugando con la restauración del estado. En el siguiente código, la posición de desplazamiento del UITableViewController se restaura, sin embargo, si tuviera que acceder a la vista de detalles (insertando una instancia de MyViewController en la pila de navegación), cuando la aplicación se reinicia, siempre vuelve a la primera vista controlador en la pila de navegación (es decir, MyTableViewController). ¿Alguien podría ayudarme a restaurar el controlador de vista correcto (es decir, 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";
}
Debido a que está creando su controlador de vista detallada en código y no lo instancia de un guión gráfico, debe implementar una clase de restauración, por lo que el proceso de restauración del sistema sabe cómo crear el controlador de vista detallada.
Una clase de restauración es solo una fábrica que sabe cómo crear un controlador de vista específico a partir de una ruta de restauración. En realidad no tiene que crear una clase separada para esto, solo podemos manejarlo en MyOtherViewController:
en MyOtherViewController.h, implemente el protocolo: UIViewControllerRestoration
Luego, cuando configure el restoreID en MyOtherViewController.m, también configure la clase de restauración:
- (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;
}
Luego necesita implementar este método en la clase de restauración (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];
}
Esto lo lleva al extremo de que el subsistema de restauración puede crear y empujar el controlador de Vista de detalles a la pila de navegación. (Lo que inicialmente preguntaste)
Extender el ejemplo para manejar el estado:
En una aplicación real, necesitaría guardar el estado y manejar restaurarlo ... Agregué la siguiente definición de propiedad a MyOtherViewController para simular esto:
@property (nonatomic, strong) NSString *selectedRecordId;
Y también modifiqué el método viewDidLoad de MyOtherViewContoller para establecer el título del Detalle en cualquier registro que el usuario haya seleccionado:
- (void)viewDidLoad
{
[super viewDidLoad];
// self.title = @"Detail";
self.title = self.selectedRecordId;
self.view.backgroundColor = [UIColor redColor];
self.view.restorationIdentifier = @"detailView";
}
Para hacer que el ejemplo funcione, configuro selectedRecordId al presionar inicialmente la vista de detalles desde 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];
}
La otra pieza faltante son los métodos en MyOtherViewController, su vista de detalle, para guardar el estado del controlador 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];
}
Ahora, en realidad, esto aún no funciona del todo. Esto se debe a que se llama al método "ViewDidLoad" en restauración antes que el método decoreRestorablStateWithCoder. Por lo tanto, el título no se establece antes de que se muestre la vista. Para solucionar esto, manejamos configurar el título para el controlador de vista de detalle en viewWillAppear: o podemos modificar el método viewControllerWithRestorationIdentifierPath para restaurar el estado cuando crea la clase como esta:
+(UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
MyOtherViewController *controller = [[self alloc] initWithNibName:nil bundle:nil];
controller.selectedRecordId = [coder decodeObjectForKey:@"selectedRecordId"];
return controller;
}
Eso es.
Algunas otras notas ...
Supongo que hiciste lo siguiente en tu delegado de la aplicación a pesar de que no vi el código.
-(BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
return YES;
}
- (BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
return YES;
}
Vi un poco de escamas cuando ejecuté el código de demostración en el simulador, conservando correctamente el desplazamiento de desplazamiento. Parecía funcionar ocasionalmente para mí, pero no todo el tiempo. Sospecho que esto puede deberse a que la restauración de MyTableViewController también debería usar un enfoque de restoreClass, ya que está instanciado en el código. Sin embargo, aún no lo he intentado.
Mi método de prueba consistía en poner la aplicación en segundo plano al volver al trampolín (necesario para guardar el estado de la aplicación), y luego reiniciar la aplicación desde XCode para simular un inicio limpio.
Como está haciendo esto en código y no a través de Storyboard, necesitará proporcionar no solo un restorationIdentifier
sino también una restorationClass
de restorationClass
para su controlador de vista detallada.
Puede dejarlo sin asignar, en cuyo caso -(UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
se llama al -(UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
al delegado de la aplicación para crear el objeto (ver el controlador con un restorationIdentifier
pero no restorationClass
).
Pruebe lo siguiente (tenga en cuenta el protocolo 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
También tenga en cuenta que este es un ejemplo muy simple, por lo general puede tener muchas más cosas interesantes en -viewControllerWithRestorationIdentifierPath:coder:
el único problema con su código es el controlador de navegación estas dos líneas
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:table];
navCon.restorationIdentifier = @"navigationController";
No sé por qué el controlador de navegación nunca obtiene su clase de restauración. Yo tuve el mismo problema. Encontré una solución alternativa para crear un controlador de navegación independiente en el guión gráfico y usar eso.
aquí está el código
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard"
bundle:nil];
UINavigationController* navigationController = [storyboard
instantiateViewControllerWithIdentifier:
@"UINavigationController"];
navigationController.viewControllers = @[myController];
Funcionará bien.
solo sigue esto http://petr-pavlik.squarespace.com/blog/2013/6/16/stare-restoration-without-storyboard