【问题标题】:UINavigationController State Restoration (without Storyboards)UINavigationController 状态恢复(无情节提要)
【发布时间】:2012-12-31 20:33:36
【问题描述】:

我一直在玩弄状态恢复。在下面的代码中,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";
}

【问题讨论】:

  • 我有一些东西可以帮到你,但我要在这里度过漫长的(澳大利亚日)周末。如果我回来后您仍然没有得到可以接受的答案,我会发布它。
  • 嗨,安迪,您的问题解决了吗?

标签: ios ios6 state-restoration


【解决方案1】:

因为您是在代码中创建详细视图控制器,而不是从 Storyboard 实例化它,所以您需要实现一个恢复类,因此系统恢复过程知道如何创建详细视图控制器。

恢复类实际上只是一个工厂,它知道如何从恢复路径创建特定的视图控制器。您实际上不必为此创建单独的类,我们可以在 MyOtherViewController 中处理它:

在MyOtherViewController.h中,实现协议:UIViewControllerRestoration

那么当你在MyOtherViewController.m中设置restoreID的时候,也要设置restore 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)中实现这个方法

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

这使您能够创建恢复子系统并将详细视图控制器推送到导航堆栈上。 (你最初问的是什么。)

扩展示例以处理状态:

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

@property (nonatomic, strong) NSString *selectedRecordId;

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

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

为了使示例正常工作,我在最初从 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];
}

另一个缺失的部分是 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”方法在 deoreRestorablStateWithCoder 方法之前在恢复时被调用。因此,在显示视图之前不会设置标题。为了解决这个问题,我们可以在 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;
    
}

就是这样。

其他一些注意事项...

即使我没有看到代码,我认为您在 App Delegate 中执行了以下操作?

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

在模拟器中运行演示代码并正确保留滚动偏移量时,我看到了一些问题。它似乎偶尔对我有用,但并非一直有效。我怀疑这可能是因为真正恢复 MyTableViewController 也应该使用 restoreClass 方法,因为它是在代码中实例化的。不过我还没试过。

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

【讨论】:

  • 感谢您的回复,但这并不是我想要的。 “你需要保存状态并处理恢复它”:我不想自己处理这个状态 - 这是 Apple 的状态恢复逻辑应该做的(参见 developer.apple.com/library/ios/#documentation/uikit/reference/… 上的“状态保存”)。另外,你不需要提供恢复类,UIKit 应该推断它(见developer.apple.com/library/ios/#documentation/iphone/…)
  • 从您发布的链接中:“在保存期间,您的应用程序负责:告诉 UIKit 它支持状态保存。告诉 UIKit 应该保留哪些视图控制器和视图。为任何保留的对象编码相关数据. 在恢复期间,您的应用程序负责: 告诉 UIKit 它支持状态恢复 提供(或创建)UIKit 请求的对象 解码您保存的对象的状态并使用它来将对象返回到之前的状态。 " -Apple 只能推断这么多。
  • 来自同一个链接 “例如,导航控制器将有关视图控制器在其导航堆栈上的顺序的信息进行编码。然后它稍后使用此信息将这些视图控制器返回到它们在堆栈。”
  • 我能够让这段代码正常工作,这很棒。我整天都在为此苦苦挣扎。但是,如果我在任一 ViewController 中抛出日志语句,我会看到 init 方法(和其他方法)实际上被调用了两次。一次来自 appWillFinish,然后再次在 viewControllerWithRestorationIdentifierPath 中。这是预期的吗?如果没有,您如何编写代码?
【解决方案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: 中发生更多有趣的事情

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-13
      • 2017-08-03
      • 1970-01-01
      • 2019-11-29
      • 1970-01-01
      • 1970-01-01
      • 2012-12-05
      • 2013-05-02
      相关资源
      最近更新 更多