子类化视图控制器
设计UI
可以使用Xcode中的storyboard文件直观地定义视图控制器的UI,也可以以编程方式来创建UI。但storyboard可以将视图的内容可视化,并根据需要自定义视图层次结构。以可视化方式构建UI界面,可以让我们快速进行更改并能看到结果,而无需构建和运行应用程序。
下图显示了一个storyboard的例子。每个矩形区域代表一个视图控制器及其相关视图,视图控制器之间的箭头是视图控制器的关系和节点。关系将容器视图控制器连接到其子视图控制器。Segue可用于在界面中的视图控制器之间导航。
处理用户交互
应用程序的响应者对象会处理传入的事件,虽然视图控制器是响应者对象,但它们很少直接处理触摸事件。相反,视图控制器通常以下列方式处理事件:
- 视图控制器定义了处理更高级别事件的操作方法。行动方法回应:
- 具体行动。控件和一些视图调用行动方法来报告特定的交互。
- 手势识别器。手势识别器调用行动方法来报告手势的当前状态。使用视图控制器处理状态变更或响应已完成的手势。
- 视图控制器观察由系统或其他对象发出的通知,通知报告更改。通知也是视图控制器更新其状态的一种方式。
- 视图控制器充当另一个对象的数据源或委托。例如,可以将它们用作
CLLocationManager
对象的代理,该对象将更新的位置发送给其委托对象。
在运行时显示视图
storyboard加载和显示视图控制器视图的过程非常简单。当需要时,UIKit会自动从stroyboard文件中加载视图。作为加载过程的一部分,UIKit执行以下任务序列:
- 使用storyboard文件中的信息实例化视图。
- 连接所有的outlets和actions。
- 将根视图分配给视图控制器的视图属性。
- 调用视图控制器的
awakeFromNib
方法。调用此方法时,视图控制器的特征集合为空,视图可能不在其最终位置。 - 调用视图控制器的
viewDidLoad
方法。在此方法中添加或者删除视图,修改布局约束,并未视图加载数据。
在屏幕上显示视图控制器的视图之前,UIKit提供了额外的机会在视图显示前后来执行需要的操作。具体来说,UIKit执行以下任务序列:
- 在视图即将出现在屏幕上前,调用视图控制器的
viewWillAppear:
方法。 - 更新视图的布局。
- 在屏幕上显示视图。
- 视图显示后,调用视图控制器的
viewDidAppear:
方法。
添加、删除或修改视图的尺寸或者位置时,也要添加和删除适用于这些视图的任何约束。对视图层次结构进行与布局相关的更改会导致UIKit将布局标记为脏。在下一个更新周期中,布局引擎使用当前布局约束条件计算视图的尺寸和位置,并将这些更改应用于视图层次结构。
管理视图布局
当视图的尺寸和位置发生变化时,UIKit将更新视图层次结构的布局信息。对于使用Auto Layout配置的视图,UIKit将使用Auto Layout引擎根据当前约束来更新布局。UIKit还会通知其他感兴趣的对象(如正在呈现的控制器)布局发生更改,以便它们可以做出相应的响应。
在布局过程中,UIKit会在几个时间点发出通知,以便我们可以执行其他与布局相关的任务。使用这些用纸来修改布局约束,或者在应用布局约束后对布局进行最终的调整。在布局过程中,UIKit为每个受影响的视图控制器执行以下操作:
- 根据需要更新视图控制器及其视图的特征集合。
- 调用视图控制器的
viewWillLayoutSubviews
方法。 - 调用当前
UIPresentationController
的containerViewWillLayoutSubviews
方法。 - 调用视图控制器的根视图的
layoutSubviews
方法。此方法默认使用可用的约束来计算新的布局信息。然后该方法遍历视图层,并调用每个子视图的layoutSubviews
方法。 - 将计算的布局信息应用于视图。
- 调用视图控制器的
viewDidLayoutSubviews
方法。 - 调用当前
UIPresentationController
对象的containerViewDidLayoutSubviews
方法。
视图控制器可以使用viewWillLayoutSubviews
和viewDidLayoutSubviews
方法来执行可能影响布局过程的附加更新。在布局之前,可以添加或删除视图,更新视图的尺寸或位置,更新约束或更新其他视图相关的属性。布局之后,可以重新加载表格数据,更新其他视图的内容,或对视图的尺寸和位置进行最终调整。
实现一个容器视图控制器
容器视图控制器是将多个视图控制器的内容合并到单个用户界面中的一种方法。容器视图控制器通常使导航变得容易,并基于现有内容创建新的用户界面类型。UIKit中容器视图控制器的示例包括UINavigationController
、UITabBarController
和UISplitViewController
,它们都可以方便在用户界面的不同部分之间进行导航。
设计自定义容器视图控制器
几乎在任何情况下,容器视图控制器都像其他任何内容视图控制器一样管理根视图和一些内容。区别在于容器视图控制器从其他视图控制器获取其内容的一部分。其获取的内容仅限于其他视图控制器的视图,这些视图嵌入在其自己的根视图层次结构中。容器视图控制器设置任何嵌入视图的尺寸和位置,但原始视图控制器仍然管理这些视图内的内容。
在设计容器视图控制器时,需要始终了解容器和包含的视图控制器之间的关系。视图控制器的关系可以帮助告知它们的内容应该如何显示在屏幕上,以及容器如何在内部管理它们。在设计过程中,需要搞清楚以下几个问题:
- 容器的作用是什么?其子视图控制器扮演什么样的角色?
- 多少个子视图控制器同时显示?
- 同级子视图控制器之间有什么关系(如果有的话)?
- 子视图控制器如何添加到容器或从容器中移除?
- 子视图控制器的尺寸和位置能改变吗?这些变化在什么情况下发生?
- 容器是否提供任何装饰或导航相关的视图?
- 容器视图控制器和子视图控制器之间如何通信?容器是否需要向
UIViewController
类定义的标准事件报告特定的事件? - 容器的外观是否可以用不同的方式配置?如果可以,如何实现?
在定义了各种对象的角色之后,容器视图控制器的实现相对简单。UIKit唯一的要求就是在容器视图控制器和任何子视图控制器之间建立正式的父子关系。父子关系确保子视图控制器收到任何相关的系统消息。除此之外,大部分的实际工作都是在包含视图的布局和管理过程中发生的,每个容器都是不同的。我们可以将视图放置在容器的内容区域的任何位置,然后根据需要调整视图的尺寸。还可以将自定义视图添加到视图层次结构中,以提供装饰或者辅助导航。
示例:导航控制器
UINavigationController
对象支持通过分层数据集来导航。导航界面一次显示一个子视图控制器。界面顶部的导航栏显示数据层次结构中当前位置,并显示后退按钮以向后移动一个级别。向下导航到另一个子视图控制器,并将子视图控制器添加到数据层次结构中。
视图控制器之间的导航由导航控制器及其子视图控制器共同管理。当用户与子视图控制器的按钮或表格进行交互后,子视图控制器要求导航控制器将新的视图控制器推入视图。子视图控制器处理新的视图控制器的内容的配置,但导航控制器管理转场动画。导航控制器还管理导航栏,该导航栏显示用于解除最顶层视图控制器的后退按钮。
下图显示了导航控制器及其视图的结构。大多数内容区域由最顶层的子视图控制器填充,只有一小部分被导航栏占用。
在紧凑和常规的环境下,导航控制器一次只显示一个子视图控制器。导航控制器调整其子视图控制器以适应可用空间。
示例:分割控制器
UISplitViewController
对象以主要-细节排列方式来显示两个视图控制器的内容。在这种排列中,一个视图控制器(主视图)的内容决定了其他视图控制器显示的细节。两个视图控制器的可见性是可配置的,但也受到当前环境的支配。在规则的水平环境中,分割视图控制器可以同时显示两个子视图控制器,或者可以隐藏主视图控制器并根据需要显示。在紧凑的环境中,分割视图控制器一次只显示一个视图控制器。
下图显示了在一个常规的水平环境中的分割视图界面及其视图的结构。分割视图控制器本身只有默认的容器视图。在这个例子中,两个子视图是并排显示的。子视图的尺寸是可配置的,主视图的可见性也是可配置的。
在Interface Builder中配置容器
要在设计时创建父子容器关系,需要将一个容器视图对象添加到视图控制器中,如下图所示。容器视图对象是代表子视图控制器内容的占位符对象。使用该视图来调整和定位与容器中其他视图相关的子视图。
当使用一个或多个容器视图加载视图控制器时,Interface Builder还会加载与这些视图关联的子视图控制器。子视图控制器必须与父视图控制器同时实例化,以便建立适当的父子关系。
如果不使用Interface Builder来设置父 - 子容器关系,则必须通过将每个子项添加到容器视图控制器来以编程方式创建这些关系。
实现自定义容器视图控制器
要实现一个容器视图控制器,必须建立容器视图控制器和其子视图控制器之间的关系。在尝试管理任何子视图控制器的视图之前,建立这些父子关系是必需的。这样做让UIKit知道容器视图控制器正在管理子视图控制器的尺寸和位置。我们可以在Interface Builder中创建这些关系,或以编程方式创建它们。
将子视图控制器添加到内容
要以编程方式将子视图控制器合并到内容中,请执行以下操作,在相关的视图控制器之间创建父子关系:
- 调用容器视图控制器的
addChildViewController:
方法,此方法告诉UIKit容器视图控制器现在正在管理子视图控制器的视图。 - 设置好子视图控制器内容的尺寸和位置,将子视图控制器的根视图添加到容器视图控制器的视图层次结构中。
- 添加任何约束来管理子视图控制器的根视图的尺寸和位置。
- 调用子视图控制器的
didMoveToParentViewController:
方法。
以下代码展示了如何在容器视图控制器中嵌入一个子视图控制器。建立父子关系后,容器视图控制器设置其子视图控制器内容的框架,并将子视图控制器的根视图添加到自己的视图层次结构中。设置子视图控制器的根视图的尺寸很重要,能够确保视图在容器中正确显示。在添加视图之后,容器视图控制器调用子视图控制器的didMoveToParentViewController:
方法,以使子视图控制器有机会响应视图所有权的更改。
- (void) displayContentController: (UIViewController*) content {
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];
}
注意,在上面的例子中,只调用了子视图控制器的didMoveToParentViewController:
方法。容器视图控制器的addChildViewController:
方法只会调用子视图控制器的willMoveToParentViewController:
方法,我们必须自己手动调用子视图控制器的didMoveToParentViewController:
。因为只有在将子视图嵌入容器的视图层次结构中后,才能调用此方法。
使用自动布局时,在将子对象添加到容器的视图层次结构后,在容器和子对象之间设置约束。添加的约束只会影响子视图控制器的根视图的尺寸和位置。请勿直接更改子视图层次结构中的根视图或任何其他视图的内容。
移除子视图控制器
要从容器视图控制器中删除子视图控制器,可以通过执行以下操作来删除视图控制器之间的父子关系:
- 调用子视图控制器的
willMoveToParentViewController:
方法且传入的值为nil
。 - 删除为子视图控制器的根视图配置的约束。
- 从容器视图控制器的视图层中移除该子视图控制器的根视图。
- 调用子视图控制器的
removeFromParentViewController
方法来结束父子关系。
删除子视图控制器会永久切断容器视图控制器与子视图控制器之间的关系。只有当不再需要引用子视图控制器时,才能移除子视图控制器。例如,当一个新视图控制器推入导航堆栈时,导航控制器并不会移除当前的子视图控制器。只有当它们从堆栈中弹出时,才会被删除。
以下代码显示了如何从容器视图控制器中删除子视图控制器。调用子视图控制器的willMoveToParentViewController:
方法且传入nil
值为子视图控制器提供了为更改做准备的机会。子视图控制器的removeFromParentViewController
方法还会调用其didMoveToParentViewController:
方法其传入nil
值。将容器视图控制器设置为nil
,也会从容器中删除子视图。
- (void) hideContentController: (UIViewController*) content {
[content willMoveToParentViewController:nil];
[content.view removeFromSuperview];
[content removeFromParentViewController];
}
子视图控制器之间的转场动画
当需要用一个子视图控制器动画替换另一个子视图控制器时,可以将子视图控制器的添加和删除合并到转场动画过程中。在执行动画前,请确保两个子视图控制器都是容器视图控制器内容的一部分,但是让当前的子视图控制器知道它即将被移除。在动画过程中,将新子视图控制器的视图移动到位并移除旧子视图控制器的视图。动画完成后,移除旧子视图控制器。
以下代码显示了如何使用转场动画将一个子视图控制器替换成另一个子视图控制器的示例。在这个示例中,transitionFromViewController:toViewController:duration:options:animations:completion:
方法会自动更新容器视图控制器的视图层次机构,不需要我们自己手动添加和删除视图。
- (void)cycleFromViewController:(UIViewController*)oldVC toViewController:(UIViewController*)newVC {
// Prepare the two view controllers for the change.
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// Get the start frame of the new view controller and the end frame
// for the old view controller. Both rectangles are offscreen.
newVC.view.frame = [self newViewStartFrame];
CGRect endFrame = [self oldViewEndFrame];
// Queue up the transition animation.
[self transitionFromViewController: oldVC toViewController: newVC
duration: 0.25 options:0
animations:^{
// Animate the views to their final positions.
newVC.view.frame = oldVC.view.frame;
oldVC.view.frame = endFrame;
}completion:^(BOOL finished) {
// Remove the old view controller and send the final
// notification to the new view controller.
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
}];
}
管理子视图控制器的外观更新
在将子视图控制器添加到容器视图控制器后,容器视图控制器会自动将外观相关的消息转发给子视图控制器。大多数情况下,这样能确保所有事件都正确发生。但是,有时默认行为可能会以无意义的顺序发送这些事件。例如,如果多个子视图控制器同时改变其视图状态,则可能需要合并这些更改,以使外观回调都以更合理的顺序同时发生。
要接管外观回调的责任,需要覆写容器视图控制器的shouldAutomaticallyForwardAppearanceMethods
方法并返回NO
。
- (BOOL) shouldAutomaticallyForwardAppearanceMethods {
return NO;
}
当有转场动画发生时,根据需要调用子视图控制器的beginAppearanceTransition:animated:
或者endAppearanceTransition
方法。
-(void) viewWillAppear:(BOOL)animated {
[self.child beginAppearanceTransition: YES animated: animated];
}
-(void) viewDidAppear:(BOOL)animated {
[self.child endAppearanceTransition];
}
-(void) viewWillDisappear:(BOOL)animated {
[self.child beginAppearanceTransition: NO animated: animated];
}
-(void) viewDidDisappear:(BOOL)animated {
[self.child endAppearanceTransition];
}
自定义容器视图控制器的几点建议
设计、开发和测试新的容器视图控制器需要时间。虽然每个视图控制器的行为是直截了当的,但整体控制起来可能相当复杂。在自定义容器控制器时,请考虑以下提示:
- 只访问子视图控制器的根视图。容器视图控制器只能访问每个子视图控制器的根视图,也就是子视图控制器的
view
属性,它不应该访问任何子视图控制器的其他视图。 - 子视图控制器应该对其容器视图控制器有最少的了解。子视图控制器应该关注自己的内容。如果容器视图控制器允许其行为受到子视图控制器的影响,则应该使用委托设计模式来管理这些交互。
- 首选使用常规视图来设计容器视图控制器。使用常规视图(而不是来自子视图控制器的视图)使我们有机会在简单的环境中测试布局约束和转场动画。当常规视图能按照预期工作时,再将常规视图替换成子视图控制器的视图。
将控制委派给子视图控制器
容器视图控制器可以将其自身外观的某些方面委托给其一个或多个子视图控制器。可以通过以下方式委派控制权:
- 让一个子视图控制器确定状态栏的样式。要将状态栏外观委托给子视图控制器,需要覆写容器视图控制器的
childViewControllerForStatusBarStyle
和childViewControllerForStatusBarHidden
方法中的一个或者两个。 - 让子视图控制器指定自己的尺寸。具有灵活布局的容器视图控制器可以使用其子视图控制器的
preferredContentSize
属性来帮助确定子视图控制器的尺寸。
支持辅助功能
iOS系统为了帮助盲人进行人机交互,设计了VoiceOver读屏技术。VoiceOver能够读出屏幕上的信息,其属于辅助功能的一部分。有关让UIViewController
支持辅助功能的详细信息,请参看Supporting Accessibility。
保存和恢复状态
视图控制器在应用程序状态保存和恢复过程中起着重要作用。状态保存会在应用程序被挂起之前保存其当前配置,以便后续应用程序启动时恢复之前的配置。将应用程序恢复到以前的配置为用户节省来时间,并提供来更好的用户体验。
保存和恢复过程大都是自动的,但是需要我们告知系统应用程序的哪些部分要保存。保存应用程序的视图控制器的步骤如下:
- (必需)将恢复标识符分配给要保留其配置的视图控制器。
- (必需)告诉系统如何在启动时创建或定位新的视图控制器对象。
- (可选)对于每个视图控制器,存储能将视图控制器返回到其之前配置所需的任何特定配置数据。
有关保存和恢复过程的概述,可以参看App Programming Guide for iOS。
标记需要保存的视图控制器
UIKit只会保存被标记需要保存的视图控制器。每个视图控制器都有一个restorationIdentifier
属性,其默认值为nil
,为该属性设置有效的字符串值将告诉UIKit应该保存视图控制器及其视图。我们可以以编程方式或在storyboard中为视图控制器分配恢复标识符。
分配恢复标识符时,一定要记住,当前视图控制器层次结构中的所有父视图控制器也必须具有恢复标识符。在保存过程中,UIKit从window的根视图控制器开始,遍历当前视图控制器层次结构。如果该层次结构中的视图控制器没有恢复标识符,则视图控制器及其所有子视图控制器和呈现的视图控制器都将被忽略。
选择有效的恢复标识符
UIKit会使用我们分配的恢复标识符去重新创建视图控制器,所以需要选择容易被代码识别的字符串来作为恢复标识符。如果UIKit无法自动创建一个视图控制器,其会要求我们自己手动创建,并为我们提供视图控制器及其所有父视图控制器的恢复标识符。这个标识符链标表示视图控制器的恢复路径,以及如何确定正在请求哪个视图控制器。恢复路径从根视图控制器开始,直至所请求的视图控制器。
恢复标识符通常是视图控制器的类名。如果在许多地方使用相同的类,则可能需要分配更有意义的值。例如,可以根据视图控制器管理的数据分配一个字符串。
每个视图控制器的恢复路径必须是唯一的。如果容器视图控制器有两个子视图控制器,容器视图控制器必须为每个子视图控制器分配一个唯一的恢复标识符。UIKit中的一些容器视图控制器会自动消除其子视图控制器的歧义,使得我们可以为每个子视图控制器使用相同的恢复标识符。例如,UINavigationController
类会根据其子视图控制器在导航堆栈中的位置给其子视图控制器添加信息。
排除视图控制器组
要在恢复过程中排除整个视图控制器组,请将父视图控制器的恢复标识符设置为nil
。下图显示了将视图控制器层次结构中的视图控制器的恢复标识符设为nil
的影响。
排除一个或者多个视图控制器并不会在随后的恢复过程中完全移除这些视图控制器。在应用程序启动时,任何视图控制器都是应用程序的默认配置的一部分,它们仍然会被创建,如下图所示。
在自动保存过程中排除视图控制器并不会阻止我们去手动保存它。在恢复归档中,保存对视图控制器的引用可以保留视图控制器及其状态信息。例如,如果图7-1中的应用程序委托保存了导航控制器的三个子项,则它们的状态将被保留。在还原期间,应用程序委托可以重新创建这些视图控制器并将其推送到导航控制器的堆栈中。
保存视图控制器的视图
一些视图具有与视图相关的附加状态信息,例如我们可能想保存滚动视图的滚动位置。当视图控制器负责提供滚动视图的内容时,滚动视图本身负责保持其视觉状态。
要保存视图的状态,需要执行以下操作:
- 为视图的
restorationIdentifier
属性分配一个有效的字符串。 - 使用也具有有效的恢复标识符的视图控制器中的视图。
- 对于表视图和集合视图,分配一个遵循
UIDataSourceModelAssociation
协议的数据源对象。
为视图分配一个恢复标识符将告知UIKit应该将视图的状态写入恢复归档,当视图控制器稍后被恢复时,UIKit还恢复具有恢复标识符的任何视图的状态。
在应用程序启动时恢复视图控制器
在应用程序启动时,UIKit会尝试将应用程序恢复到之前的状态。此时,UIKit会要求应用程序创建(或定位)包含之前保存的用户界面的视图控制器。UIKit在尝试定位视图控制器时,会按照以下顺序去搜索:
- 如果视图控制器有恢复类,UIkit会要求该类提供视图控制器。UIKit会调用关联的恢复类的
viewControllerWithRestorationIdentifierPath:coder:
方法来检索视图控制器。如果该方法返回nil
,UIkit会假定应用程序不用重新创建视图控制器并且会停止查找。 - 如果视图控制器没有关联恢复类,UIKit会要求应用程序委托提供视图控制器。UIKit调用应用程序委托的
application:viewControllerWithRestorationIdentifierPath:coder:
方法来查找没有恢复类的视图控制器。如果该方法返回nil
,UIKit将尝试隐式查找视图控制器。 - 如果具有正确恢复路径的视图控制器已经存在,UIKit就会使用该视图控制器对象。如果应用程序在启动时创建视图控制器(以编程方式或者从storyboard中加载),且视图控制器具有恢复标识符,则UIKit会根据其恢复路径隐式查找它们。
- 如果视图控制器最初是从storyboarrd中加载的,则UIKit使用保存的storyboard信息来定位和创建它。UIKit将有关视图控制器的storyboard信息保存在恢复归档中,在恢复时,UIKit使用该信息来定位相同的storyboard文件,并且会在使用任何其他方式都没有查找到视图控制器的情况下实例化相应的视图控制器。
为视图控制器分配一个恢复类可以避免UIKit隐式地检索该视图控制器。使用恢复类可以更好地控制是否真的要创建视图控制器。例如,如果恢复类确定不应该重新创建视图控制器,则viewControllerWithRestorationIdentifierPath:coder:
方法可以返回nil
。当没有恢复类时,UIKit会尽其所能找到或者创建视图控制器,并将其恢复。
当使用恢复类时,viewControllerWithRestorationIdentifierPath:coder:
方法应该创建一个类的新实例对象,执行最低限度的初始化,并返回结果对象。以下代码展示了一个如何使用此方法从storyboard中加载视图控制器的例子。由于视图控制器最初是从storyboard加载的,因此此方法使用UIStateRestorationViewControllerStoryboardKey
键值从存档中获取storyboard。注意,此方法不会尝试配置视图控制器的数据内容。当视图控制器的状态被解码后,才会去配置视图控制器的数据内容。
+ (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
数组中的最后一项,并将其分配给视图控制器。
对于在应用程序启动时从main storyboard文件中创建的对象,请勿为每个对象创建新的实例。请让UIKit隐式查找这些对象,或者使用应用程序委托对象的application:viewControllerWithRestorationIdentifierPath:coder:
方法来查找现有对象。
编码和解码视图控制器的状态
对于每个要保存的对象,UIKit都会调用对象的encodeRestorableStateWithCoder:
方法,使其有机会保存其状态。在恢复过程中,UIKit会调用对应的decodeRestorableStateWithCoder:
方法来解码该状态并将其用于对象。对于视图控制器,这些方法的实现是可选的,但建议实现。可以使用它们来保存和恢复以下类型的信息:
- 关联引用所显示的任何数据(不是数据本身)。
- 对于容器视图控制器,关联引用其子视图控制器。
- 与当前选择有关的信息。
- 对于具有用户可配置视图的视图控制器,提供关于该视图当前配置的信息。
在编码和解码方法中,可以对编码器支持的对象和任何数据类型进行编码。对于除视图和视图控制器以外的所有对象,对象必须遵循NSCoding
协议,并使用该协议的方法来写入其状态。对于视图和视图控制器,编码器并不使用NSCoding
协议来保存对象的状态。取而代之的是,编码器保存对象的恢复标识符并将其添加到可保存对象的列表中,这会导致对象的encodeRestorableStateWithCoder:
方法被调用。
在实现视图控制器的encodeRestorableStateWithCoder:
和decodeRestorableStateWithCoder:
方法时,必须调用super
的该方法。调用super
的该方法让父类有机会保存和恢复任何附加信息。以下代码展示来这些方法的一个示例实现,它们保存用于标识指定视图控制器的数字值。
- (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使用这些键来存储关于视图控制器状态的附加信息。
有关实现视图控制器的编码和解码的更多信息,请参看UIViewController Class Reference。
保存和恢复视图控制器的几点建议
在视图控制器中添加对状态保存和恢复的支持时,请考虑以下准则:
- 请记住,我们可能不想保留所有视图控制器。在某些情况下,保留视图控制器可能没有意义。例如,如果应用程序正在显示更改,则可能需要取消操作并将应用程序还原到上一个屏幕。在这种情况下,我们不需要保留要求输入新密码信息的视图控制器。
- 避免在恢复过程中换掉视图控制器对象所属的类。状态保存系统对它保存的视图控制器的类进行进行编码。在恢复过程中,如果应用程序返回的对象的类不匹配(或者不是原始对象的子类),则系统不会要求视图控制器解码任何状态信息。
- 状态保存系统希望我们按照预期使用视图控制器。恢复过程依赖于视图控制器的包含关系来重建界面。如果我们没有正确使用容器视图控制器,保存系统将找不到视图控制器。例如,除非在相应的视图控制器之间存在包含关系,否则不要将视图控制器的视图嵌入到不同的视图中。