视图控制器在状态保存和恢复过程中起重要作用。 状态保留记录您的应用程序在暂停之前的配置,以便可以在后续应用程序启动时恢复配置。 将应用程序恢复到其先前的配置可为用户节省时间,并提供更好的用户体验。
保存和恢复过程大多是自动的,但你需要告诉iOS应用程序的哪些部分保留。 保存应用程序视图控制器的步骤如下:
- (必需)将恢复标识符分配给要保留其配置的视图控制器;
- (必需)告诉iOS如何在启动时创建或定位新的视图控制器对象;
- (可选)对于每个视图控制器,存储将该视图控制器返回到其原始配置所需的任何特定配置数据;
标记视图控制器以进行保存
UIKit仅保留您要让它保留的视图控制器。 每个视图控制器都有一个restorationIdentifier属性,默认值为nil。 将该属性设置为有效字符串会告诉UIKit应保留视图控制器及其视图。 您可以以编程方式或在故事板文件中分配恢复标识符。
当分配恢复标识符时,请记住视图控制器层次结构中的所有父视图控制器也必须具有恢复标识符。 在保存过程中,UIKit从窗口的根视图控制器开始,并遍历视图控制器层次结构。 如果该层次结构中的视图控制器不具有恢复标识符,则忽略视图控制器及其所有子视图控制器和呈现的视图控制器。
选择有效恢复标识符
UIKit使用您的恢复标识符字符串以后重新创建视图控制器,因此选择可以容易地识别您的代码的字符串。 如果UIKit无法自动创建一个视图控制器,它会要求您创建它,为您提供视图控制器及其所有父视图控制器的恢复标识符。 这个标识符链表示视图控制器的恢复路径,是你如何确定请求哪个视图控制器。 恢复路径在根视图控制器处开始,并且包括直到并且包括所请求的视图控制器的每个视图控制器。
恢复标识符通常只是视图控制器的类名。 如果在许多地方使用相同的类,则可能需要分配更有意义的值。 例如,您可以基于由视图控制器管理的数据分配字符串。
每个视图控制器的恢复路径必须是唯一的。 如果容器视图控制器有两个子节点,则容器必须为每个子节点分配唯一的恢复标识符。 UIKit中的一些容器视图控制器自动消除其子视图控制器的歧义,允许您为每个子项使用相同的恢复标识符。 例如,UINavigationController类根据其在导航堆栈中的位置向每个子项添加信息。
不包括视图控制器组
要从恢复过程中排除整个视图控制器组,请将父视图控制器的恢复标识符设置为nil。 图7-1显示了将恢复标识符设置为nil对视图控制器层次结构的影响。 缺少保留数据阻止了视图控制器以后被恢复。
图7-1从自动保存过程中排除视图控制器
排除一个或多个视图控制器在后续还原期间不会完全删除它们。 在启动时,仍然创建作为应用程序默认设置的一部分的任何视图控制器,如图7-2所示。 这样的视图控制器以其默认配置重新创建,但仍然创建它们。
图7-2加载默认的视图控制器集
从自动保存过程中排除视图控制器不会阻止您手动保留它。 在恢复归档中保存对视图控制器的引用会保留视图控制器及其状态信息。 例如,如果图7-1中的应用程序委托保存了导航控制器的三个子代,则它们的状态将被保留。 在恢复期间,应用程序代理可以重新创建这些视图控制器并将它们推送到导航控制器的堆栈。
保留视图控制器的视图
一些视图具有与视图相关但与父视图控制器无关的附加状态信息。 例如,滚动视图具有您可能希望保留的滚动位置。 虽然视图控制器负责提供滚动视图的内容,但是滚动视图本身负责保持其可视状态。
要保存视图的状态,请执行以下操作:
- 为视图的restoredIdentifier属性分配一个有效的字符串。
- 使用来自还具有有效恢复标识符的视图控制器的视图。
- 对于表视图和集合视图,请分配采用UIDataSourceModelAssociation协议的数据源。
为视图分配恢复标识符告诉UIKit它应该将该视图的状态写入保留存档。 当视图控制器以后恢复时,UIKit还恢复具有恢复标识符的任何视图的状态。
在启动时恢复视图控制器
在启动时,UIKit尝试将您的应用恢复到之前的状态。 当时,UIKit要求您的应用程序创建(或定位)包含保留的用户界面的视图控制器对象。 UIKit在尝试查找视图控制器时按以下顺序进行搜索:
- 如果视图控制器有一个恢复类,UIKit要求该类提供视图控制器。 UIKit调用关联的恢复类的viewControllerWithRestorationIdentifierPath:coder:方法来检索视图控制器。 如果该方法返回nil,则假定应用程序不想重新创建视图控制器,UIKit停止查找它。
- 如果视图控制器没有恢复类,UIKit要求应用程序代理提供视图控制器。 UIKit调用应用程序:viewControllerWithRestorationIdentifierPath:coder:您的应用程序委托的方法来查找没有恢复类的视图控制器。 如果该方法返回nil,UIKit试图隐式地查找视图控制器。
- 如果具有正确恢复路径的视图控制器已存在,UIKit将使用该对象。 如果您的应用程序在启动时创建视图控制器(以编程方式或通过故事板加载它们),并且这些视图控制器有恢复标识符,UIKit隐式地基于它们的恢复路径找到它们。
- 如果视图控制器最初从故事板文件加载,UIKit使用保存的故事板信息来定位和创建它。 UIKit将有关视图控制器的storyboard的信息保存在恢复存档中。 在恢复时,UIKit使用该信息来定位相同的故事板文件,并且如果没有通过任何其他方式找到视图控制器,则实例化相应的视图控制器。
向视图控制器分配恢复类可以防止UIKit隐含地搜索该视图控制器。 使用恢复类可以更好地控制是否要创建视图控制器。 例如,如果您的类确定不应重新创建视图控制器,则您的viewControllerWithRestorationIdentifierPath:coder:方法可以返回nil。 当没有恢复类时,UIKit将尽其所能找到或创建视图控制器并恢复它。
当使用恢复类时,您的viewControllerWithRestorationIdentifierPath:coder:方法应该创建一个类的新实例,执行最小初始化,并返回结果对象。 清单7-1显示了如何使用此方法从故事板加载视图控制器的示例。 因为视图控制器最初从故事板加载,所以此方法使用UIStateRestorationViewControllerStoryboardKey键从归档中获取故事板。 请注意,此方法不会尝试配置视图控制器的数据字段。 该步骤稍后发生在视图控制器的状态被解码时。
清单7-1在恢复期间创建一个新的视图控制器
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder {
MyViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb) {
vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"MyViewController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [MyViewController class];
}
return vc;
}
重新分配恢复标识符和恢复类是手动重新创建视图控制器时采用的好习惯。 恢复恢复标识符的最简单的方法是抓取identifierComponents数组中的最后一个项目,并将其分配给视图控制器。
对于在启动时从应用程序的主storyboard文件创建的对象,请不要创建每个对象的新实例。 让UIKit隐式找到这些对象或使用应用程序:viewControllerWithRestorationIdentifierPath:coder:您的应用程序委托的方法来查找现有对象。
编码和解码视图控制器的状态
对于每个要保存的对象,UIKit调用对象的encodeRestorableStateWithCoder:方法给它一个保存其状态的机会。 在恢复过程中,UIKit调用匹配的decodeRestorableStateWithCoder:方法来解码该状态并将其应用于对象。 这些方法的实现是可选的,但建议为您的视图控制器。 您可以使用它们来保存和恢复以下类型的信息:
- 对任何正在显示的数据的引用(不是数据本身)
- 对于容器视图控制器,引用其子视图控制器
- 有关当前选择的信息
- 对于具有用户可配置视图的视图控制器,有关该视图的当前配置的信息。
在编码和解码方法中,您可以对对象和编码器支持的任何数据类型进行编码。 对于除视图和视图控制器之外的所有对象,对象必须采用NSCoding协议并使用该协议的方法来写入其状态。 对于视图和视图控制器,编码器不使用NSCoding协议来保存对象的状态。 相反,编码器保存对象的恢复标识符并将其添加到可保留对象的列表中,这导致调用该对象的encodeRestorableStateWithCoder:方法。
你的视图控制器的encodeRestorableStateWithCoder:和decodeRestorableStateWithCoder:方法必须在其实现的某个点调用super。 调用super给父类一个机会来保存和恢复任何附加信息。 清单7-2显示了保存用于标识指定视图控制器的数值的这些方法的示例实现。
代码7-2对视图控制器的状态进行编码和解码。
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
[coder encodeInt:self.number forKey:MyViewControllerNumber];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
self.number = [coder decodeIntForKey:MyViewControllerNumber];
}
编码器对象在编码和解码过程中不共享。 每个具有可保存状态的对象接收其自己的编码器对象。 使用唯一的编码器意味着你不必担心你的键之间的命名空间冲突。 但是,不要使用UIApplicationStateRestorationBundleVersionKey,UIApplicationStateRestorationUserInterfaceIdiomKey和UIStateRestorationViewControllerStoryboardKey键名称自己。 UIKit使用这些键来存储有关视图控制器状态的附加信息。
保存和恢复视图控制器的提示
当您在视图控制器中添加对状态保存和恢复的支持时,请考虑以下准则:
- 请记住,您可能不想保留所有视图控制器。 在某些情况下,保留视图控制器可能没有意义。 例如,如果应用程序正在显示更改,则可能需要取消操作并将应用程序恢复到上一屏幕。 在这种情况下,您不会保留要求新密码信息的视图控制器。
- 避免在恢复过程中交换视图控制器类。 状态保存系统对它保存的视图控制器的类进行编码。 在恢复期间,如果您的应用程序返回其类与原始对象不匹配(或不是其子类)的对象,则系统不会要求视图控制器解码任何状态信息。 因此,换出完全不同的旧视图控制器不会恢复对象的完整状态。
- 状态保留系统期望您使用它们的意图的视图控制器。 恢复过程依赖于视图控制器的包含关系来重建接口。 如果不正确使用容器视图控制器,保存系统将无法找到您的视图控制器。 例如,从不将视图控制器的视图嵌入在不同的视图中,除非在相应的视图控制器之间存在包含关系。