翻译:iOS视图控制器编程指南(七)——保存和恢复状态(Pres
视图控制器在保存和恢复过程中起着非常重要的作用。状态保存记录应用中断前的配置,在随后应用启动恢复配置。将应用恢复到先前的配置可以为用户节省时间,并提供一个更好的用户体验。
保存和恢复过程是自动的,但你需要告诉iOS,需要保留应用的哪些部分。保存应用视图控制器的步骤如下:
· (必须)分配恢复标示符到你想保存的视图控制器上,参见标记保存视图控制器( Tagging View Controllers for Preservation)
· (必须)告诉iOS如何在启动时创建或查找新视图控制器对象,参见启动时恢复视图控制器( Restoring View Controllers at Launch Time)。
· (可选)每个视图控制器,存储任何特定配置数据使视图控制器返回到原来的配置,参见视图控制器状态的编码和解码(Encoding and Decoding Your View Controller’s State)。
保存和恢复的过程的概述,参见iOS应用编程指南(App Programming Guide for iOS)。
标记保存视图控制器
UIKit只保存你告诉它要保存的视图控制器。每个视图控制器有一个restorationIdentifier属性,这个属性的默认值为nil。设置这个属性值为有效字符串,告诉UIKit视图控制器和视图应该要保存。可以以编程的方式或者在storyboard文件中设置恢复标示符。
当分配恢复标示符时,在视图层级结构中的所有的父视图控制器必须有恢复标示符。在保存过程中,UIKit从窗口的根视图控制器开始并遍历视图控制器层级结构。如果该层级结构中一个视图控制器没有恢复标示符,该视图控制器和其所有子视图控制器和present的视图控制器都会被忽略。
选择有效的恢复标示符
UIKit使用恢复标示符字符串在之后重新创建视图控制器,所以必须选择代码容易识别的字符串。如果UIKit不能自动创建一个视图控制器,要求你创建,UIKit会提供视图控制器和其所有父视图控制器的恢复标示符。这一串的标示符代表视图控制器的恢复路径,及如何决定哪些视图控制器被请求。恢复路径从根视图控制器开始,包括呈现的每个视图控制器和被请求的视图控制器。
恢复标示图通常以视图控制器类名命名。如果你在很多地方使用相同的类,你可能希望分配更有意义的值。例如,你可以基于视图控制器管理的数据来指定字符串。
每个视图控制器的恢复路径必须是唯一的。如果一个容器视图控制器有两个子视图,容器必须为每个子视图控制器分配一个独特的恢复标示符。一些UIKit中容器视图控制器会自动消除歧义子视图控制器,允许为每个子视图控制器使用相同恢复标示符。例如,类根据每个子视图控制器在导航堆栈中的位置添加信息。关于一个给定视图控制器的行为更多信息,参见相应的类引用。
如何使用恢复标示符和恢复路径创建视图控制器的更多信息,参见启动时恢复视图控制器(Restoring View Controllers at Launch Time)。
不包括视图控制器组
在恢复过程中不包括整个视图控制器组,设置父视图控制器的恢复标示符为nil。图7-1展示了设置恢复标示符为nil对视图层级结构的影响。缺少保存数据,在稍后无法恢复视图控制器。
图7-1 自动保存过程中不包括的视图控制器在随后的恢复过程中,并不完全删除其中不包括的一个或多个视图控制器。在启动时,任何视图控制器都是应用的一部分,默认创建设置,如图7-2所示。按照默认配置创建视图控制器。
图7-2 加载默认视图控制器组自动保存过程中,可以手动保存不包括的视图控制器。保存视图控制器的引用即保存视图控制器和其状态信息。例如,如果图7-1中的应用代理保存导航控制器的三个子视图控制器,他们的状态将被保存下来。在恢复过程中,应用代理可以重现创建这些视图控制器并push他们到导航控制器的堆栈上。
保存视图控制器的视图
一些视图有更多与视图而不是父视图控制器相关的状态信息。例如,你希望保存滚动视图的一个滚动位置。视图控制器负责提供滚动视图的内容,而滚动视图本身负责保存其视觉状态。
保存视图状态,执行以下操作:
· 指定为 restoration Identifier 属性一个有效的字符串。
· 使用具有有效标示符的视图控制器的视图。
· 对于表视图和集合视图,指定采用UI Data Source Model Association 协议的数据源
将标示符分配给视图告诉UIKit应该保存视图状态。当稍后恢复视图控制器,UIKit也恢复有恢复标示符的视图的状态。
在启动时恢复视图控制器
在启动时,UIKit试图恢复你的应用到之前的状态。那时,UIKit访问你的应用创建(或确定)用户界面保存的视图控制器对象。当定位视图控制器时,UIKit按照以下顺序搜索:
**如果视图控制器有一个恢复类,UIKit访问该类提供视图控制器。UIKit调用恢复类的 [viewControllerWithRestorationIdentifierPath:coder:](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIViewControllerRestoration_protocol/index.html#//apple_ref/occ/intfcm/UIViewControllerRestoration/viewControllerWithRestorationIdentifierPath:coder:) 方法来检索视图控制器。**如果该方法返回nil,则假设应用并不希望重新创建视图控制器,UIKit停止寻找该视图控制器。
**如果视图控制器没有恢复类,UIKit要求应用代理提供视图控制器。**UIKit调用应用代理的[application:viewControllerWithRestorationIdentifierPath:coder:](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:viewControllerWithRestorationIdentifierPath:coder:)方法寻找没有恢复类的视图控制器。如果该方法返回nil,UIKit尝试找到隐藏的视图控制器。
**如果有正确恢复路径的视图控制器已经存在,UIKit使用该对象。**如果你的应用在启动时(无论是以编程的方式还是通过storyboard加载的方式)创建视图控制器,并且这些视图控制器有恢复标识符,UIKit基于他们的恢复路径隐式的查找他们。
**如果视图控制器最初是从storyboard文件加载,UIKit使用保存的storyboard信息来定位并创建它。**UIKit保存视图控制器的storyboard信息到恢复文件中。在恢复时,UIKit使用这些信息来定位相同的storyboard文件,如果通过其他任何方式没有找到视图控制器,则实例化相应视图控制器。
将恢复类分配给视图控制器可以防止UIKit隐式的搜索该视图控制器。使用恢复类让你可以控制是否真正创建一个视图控制器。例如,如果你的类决定不再重新创建视图控制器,viewControllerWithRestorationIdentifierPath:coder:
方法可以返回nil。当不存在恢复类,UIKit会尽可能找到或者创建该视图控制器来恢复它。
当使用一个恢复类,viewControllerWithRestorationIdentifierPath:coder:
方法应该创建该类的新实例,执行初始化,并返回结果对象。列表7-1 展示了如何使用此方法从storyboard中加载视图控制器的例子。因为该视图控制器最初从storyboard加载,该方法使用UIStateRestorationViewControllerStoryboardKey key从文件中找到storyboard。注意:该方法不配置视图控制器的数据字段。当解码视图控制器的状态时,发生这一步。
列表7-1 在恢复过程中创建一个新视图控制器
<pre><code>
+(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;
}
</pre></code>
当手动重新创建视图控制器,重新分配恢复标识符和恢复类是个好习惯。恢复恢复标识符的最简单的方法是获取identifierComponents
数组中的最后一项,并将其分配给你的视图控制器。
在启动时由应用主要storyboard文件中创建的对象,不需要为每个对象创建新实例。让UIKit隐式的查找这些对象或者使用应用代理的application:viewControllerWithRestorationIdentifierPath:coder:
方法查找这些存在的对象。
编码和解码视图控制器状态
对于每个要保留的对象,UIKit调用对象的encodeRestorableStateWithCoder:方法来保存其状态。在恢复过程中,UIKit调用对应的decodeRestorableStateWithCoder: 方法来解码该状态并将其用于该对象。这些方法的实现是可选的,但是建议,实现。你可能使用它们来保存和恢复以下类型的信息:
· 显示数据的引用(不是数据本身)
· 对于容器视图控制器,其子视图控制器的引用
· 当前选择的信息
· 对于用户可配置视图的视图控制器,当前配置视图的信息
在你的编码和解码方法中,你可以编码对象和编码器支持的任何数据类型。除了视图和视图控制器的其他所有对象必须采用 NSCoding协议并使用协议中的方法来保存其状态。对于视图和视图控制器,编码器不使用 NSCoding协议来保存对象状态。相反,编码器保存该对象的恢复标识符并将其添加到保存对象列表,这将导致该对象的encodeRestorableStateWithCoder:
方法被调用。
视图控制器的encodeRestorableStateWithCoder:
和decodeRestorableStateWithCoder:
方法必须在实现的某个位置调用super方法。调用super方法让父类有机会保存和恢复任何额外的信息。列表7-2展示了这些方法的实现例子,该方法保存一个数值用于识别指定视图控制器。
列表7-2 编码和界面视图控制器状态
<pre><code>
-(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];
}
</pre></code>
在编码和解码过程中,编码对象不共享。每个保存状态的对象接收自己的编码对象。使用独特的编码器表明你不用担心key之间的命名冲突。然而,不要使用UIApplicationStateRestorationBundleVersionKey,UIApplicationStateRestorationUserInterfaceIdiomKey和UIStateRestorationViewControllerStoryboardKey key来命名。UIKit使用这些key来存储视图控制器状态的额外信息。
关于视图控制器编码解码方法的更多信息,参见UIViewcontroller类引用(UIViewController Class Reference)。
保存和恢复视图控制器技巧
在视图控制器中添加状态保存与恢复,考虑以下指南:
· 请记住可能不希望保存所有视图控制器。在某些情况下,保存某个视图控制器没有意义。例如,如果应用显示一个变更,你希望取消操作并恢复到应用的前一个界面。在这种情况下,你不用保存该视图控制器。
· 恢复过程中避免交换视图控制器类。状态保存系统为其保存的视图控制器类编码。在恢复期间,如果你的应用返回一个不匹配原始对象(或不是原始对象的子类)的对象,系统不要求视图控制器解码任何状态信息。因此,划掉旧的视图控制器换成完全不同的视图控制器,这个过程不恢复该对象的全部状态。
· 状态保存系统希望你有意的使用视图控制器。恢复过程依赖视图控制器的控制关系来重建你的界面。如果你没有正确使用容器视图控制器,保存系统不能找到你的视图控制器。例如,除非相应的视图控制器之间包含某种关系,否则不要在不同的视图中嵌入一个视图控制器的视图。