iOS 状态保存和容器视图

Posted

技术标签:

【中文标题】iOS 状态保存和容器视图【英文标题】:iOS state preservation and container views 【发布时间】:2013-04-03 23:17:55 【问题描述】:

我在情节提要中有一个使用容器视图的视图控制器。两者都设置了恢复标识符。父母正在被保存和恢复得很好。然而孩子不是。子视图控制器上都没有调用-encodeRestorableStateWithCoder:-decodeRestorableStateWithCoder:

保存使用视图容器创建的子视图控制器的正确方法是什么?我可以将子视图控制器保存在父母-encodeRestorableStateWithCoder: 中,这将导致它被保存,但我无法在恢复期间使用它。

【问题讨论】:

您是否使用 addChildViewController: 将孩子添加到父级? 在将子视图控制器添加到父视图控制器后,是否调用了 didMoveToParentViewController: 方法? 容器视图应该自动执行此操作。我从父母 childViewControllers 那里得到了对它的引用。 遇到了同样的问题,我通过情节提要在容器中添加了 vc,但没有接到任何电话。这样做的正确方法是什么? 【参考方案1】:

容器视图控制器“不会自动保存对任何包含的子视图控制器的引用。如果您正在实现自定义容器视图控制器,则必须自己编码子视图控制器对象才能保留它们 em>”。

我发现了一些简单的规则:

1.Embedded(child) 视图控制器应该已经在状态保存过程中创建并添加到父视图控制器。因此,如果您使用故事板,则无需执行任何操作,否则您必须实例化子视图控制器并手动添加:

-(void)viewDidLoad

    [super viewDidLoad];
    NSLog(@"Did load");
    MyChildViewController *childViewController = [MyChildViewController new];
    [self addChildViewController:childViewController];
    [childViewController didMoveToParentViewController:self];
    self.childVC = childViewController;

您可以在-viewDidLoad 或更高版本中添加子视图。为此使用self.childVC.view.frame = [self frameForChildController]; [self.view addSubview:self.childVC.view];

2.您无需自己将子视图控制器保存在父级的-encodeRestorableStateWithCoder: 中,但您应该使用-encodeObject:forKey: 对该对象的引用 进行编码。如果你有参考,你可以这样做:

-(void)encodeRestorableStateWithCoder:(NSCoder *)coder

    NSLog(@"Encode");
    UIViewController *childViewController = self.childVC;
    [coder encodeObject:childViewController forKey:@"ChildVC"];
    [super encodeRestorableStateWithCoder:coder];

如果您使用 Storyboard,请参阅 https://***.com/a/13279703/2492707 以获取对子 VC 的引用。或者你可以像这样写一些简单的东西:

-(void)encodeRestorableStateWithCoder:(NSCoder *)coder

    NSLog(@"Encode");
    UIViewController *childViewController = [self.childViewControllers objectAtIndex:0]; //[self.childViewControllers lastObject];
    [coder encodeObject:childViewController forKey:@"ChildVC"];
    [super encodeRestorableStateWithCoder:coder];

3.嵌入式(子)视图控制器应该已经创建并在状态恢复过程中添加到父视图控制器。所以,如果你在第一段中做了所有事情,这里就没有什么可做的了。

4.“然而,在这种情况下,我们不解码子视图控制器。我们可以,但实际上我们不需要它。MyChildViewController 对象将恢复它自己的状态。我们仅对该引用进行编码,以便让运行时将链向下传递到 MyChildViewController 实例并对其进行保存和恢复”。

-(void)decodeRestorableStateWithCoder:(NSCoder *)coder

    NSLog(@"Decode");
    [super decodeRestorableStateWithCoder:coder];

This 这本书帮助我理解容器视图的状态保存。还有look 为本书提供了一个很好的例子

【讨论】:

总结一下,如果您使用故事板容器视图(带有嵌入 segue 的视图),那么您需要做的就是使用来自父母 encodeRestorableStateWithCoder: 的孩子调用 encodeObject:forKey:。获得对孩子的引用的最佳方法是在prepareForSegue:sender: 中进行嵌入segue。无需手动解码孩子,因为恢复过程会找到已经存在的孩子并从存档中恢复其状态。但是,如果您不使用情节提要(即手动添加子项),那么您可能也需要实现该查找机制。 您说嵌入式子控制器应该已经创建并在状态恢复过程中添加到父视图中。但是我们如何访问它呢?因为如果它已经被实例化了,那么就不应该像你展示的那样在 viewDidLoad 中再做一次【参考方案2】:

我认为答案在documentation 据说:

" UIViewController 类保存对呈现的视图控制器和用于创建视图控制器的情节提要(如果有)的引用。视图控制器还要求其视图层次结构中的视图保存任何相关信息。但是,此类不会自动保存对任何包含的子视图控制器的引用。如果您正在实现自定义容器视图控制器,则必须自己编码子视图控制器对象才能保留它们。"

所以你可以这样做:

-(void)encodeRestorableStateWithCoder:(NSCoder *)coder 
    [super encodeRestorableStateWithCoder:coder];
    [self.myChildViewController encodeRestorableStateWithCoder:coder];


-(void)decodeRestorableStateWithCoder:(NSCoder *)coder 
    [super decodeRestorableStateWithCoder:coder];
    [self.myChildViewController decodeRestorableStateWithCoder:coder];

并且在 MyChildViewController 中不要调用 super :)

【讨论】:

我的问题是关于使用视图容器,而不是一般的子视图控制器。 好的,在我的情况下,我使用容器视图来添加子视图控制器。首先,我在 -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 中获得对创建的子视图控制器的引用,然后按照前面的解释手动解码和编码。 有没有办法解码 MyChildViewController 的状态,它自己调用 [super decodeRestorableStateWithCoder:coder] -(void)decodeRestorableStateWithCoder:(NSCoder *)coder?例如,我想将 MyChildViewController 用作子视图控制器,同时用作独立视图控制器(在 pushViewController 中用作参数)。 您不想将子视图控制器的状态与父视图控制器的状态混为一谈,这就是这段代码要做的事情。相反,将子视图控制器整体编码为一个单独的对象。 这个解决方案实际上并没有为子进程使用状态保存/恢复机制,它只是调用一些自定义代码(您错误地称为 encodeRestorableStateWithCoder 和 decodeRestorableStateWithCoder)来保存和恢复其状态作为父母的状态。

以上是关于iOS 状态保存和容器视图的主要内容,如果未能解决你的问题,请参考以下文章

UIKit 状态保存不恢复滚动偏移

无法加载视图状态。正在加载视图状态的控制树必须与用于保存视图状态的控制树匹配

使用多个 RecyclerView 和其他视图保存/恢复 NestedScrollView 的状态

保存对容器的修改

容器视图控制器状态栏 iOS 7

iOS 6 状态保存和恢复的自动化测试