[TOC=5]
* * * * *
>原文链接 :[Defining Your Subclass](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/DefiningYourSubclass.html#//apple_ref/doc/uid/TP40007457-CH7-SW1)
你使用 UIViewController 的自定义子类来呈现你的应用程序的内容。大多数自定义视图控制器是内容视图控制器,也就是说,它们拥有所有视图,并负责管理这些视图中的数据。相比之下,容器视图控制器并不拥有它的所有视图;它的一些视图由其他视图控制器管理。定义内容和容器视图控制器的大多数步骤是相同的,并将在后面的小节中讨论。
对于内容视图控制器,最常见的父类如下所示:
* 当你的视图控制器的主视图是一个表时,使用 UITableViewController。
* 当你的视图控制器的主视图是一个集合视图时,使用 UICollectionViewController。
* 对所有其他视图控制器使用 UIViewController。
对于容器视图控制器,父类取决于您是在修改现有的容器类还是创建自己的类。对于现有的容器,选择您想要修改的任何视图控制器类。对于新的容器视图控制器,建议你子类化 UIViewController 。有关创建容器视图控制器的其他信息,请参见 [Implementing a Container View Controller](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html#//apple_ref/doc/uid/TP40007457-CH11-SW1)。
### 定义UI
用Xcode中的故事板文件来定义视图控制器的UI。虽然您也可以通过编程方式创建UI,但是故事板是一种很好的可视化视图控制器内容的方法,并且可以根据不同的环境定制您的视图层次结构(根据需要)。可视化地构建UI可以让您快速做出更改,并让您看到结果,而无需编译和运行您的应用程序。
图 4-1 显示了一个故事板的例子。每个矩形区域表示一个视图控制器及其相关视图。视图控制器之间的箭头是视图控制器关系和segue。关系将一个容器视图控制器连接到它的子视图控制器。segue让你在界面的视图控制器之间导航。
###### 图 4-1 一个故事板包含一组视图控制器和视图
![](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/Art/storyboard_bird_sightings_2x.png)
每个新项目都有一个主故事板,它通常包含一个或多个视图控制器。您可以将新的视图控制器添加到您的故事板中,将它们从库拖到您的画布上。新的视图控制器最初没有相关的类,因此您必须使用标识检查器分配一个类。
使用故事板编辑器来完成以下操作:
* 添加、排列和配置视图控制器的视图。
* 连接 outlets 和 actions,参考 [Handling User Interactions](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/DefiningYourSubclass.html#//apple_ref/doc/uid/TP40007457-CH7-SW11) 。
* 在视图控制器之间创建关系和 segue ,参考 [Using Segues](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html#//apple_ref/doc/uid/TP40007457-CH15-SW1) 。
* 为不同大小的界面定制您的布局和视图,参考 [Building an Adaptive Interface](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/BuildinganAdaptiveInterface.html#//apple_ref/doc/uid/TP40007457-CH32-SW1)。
* 添加手势识别器来处理用户与视图的交互;参考 Event Handling Guide for iOS(找不到此文链接) 。
如果你是使用故事板来构建界面的新手,那么你可以在开始开发iOS应用程序的时候,找到创建基于故事板的界面的一步一步的指导,参考 [Start Developing iOS Apps (Swift)](https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/)。
### 处理用户交互
应用程序的响应对象处理传入事件并采取适当的动作。尽管视图控制器是响应对象,但它们很少直接处理触摸事件。相反,视图控制器通常以以下方式处理事件。
* 视图控制器定义了处理更高级别事件的操作方法。 用来响应:
* 指定的 action 。 控件和一些视图调用一个 action 方法来响应特定的交互。
* 手势识别器。 手势识别器调用方法来报响应手势的当前状态。 使用视图控制器处理状态更改或响应已完成的手势。
* 视图控制器观察由系统或其他对象发送的通知。通知报告更改,并且是视图控制器更新其状态的一种方式。
* 视图控制器充当另一个对象的数据源或委托。视图控制器通常用于管理 UITableView 和 UICollectionView 的数据。您还可以将它们用作其他对象的委托,例如 CLLocationManager ,该对象将更新的位置值发送给它的委托。
对事件的响应常常涉及到更新视图的内容,更新视图的内容需要拥有这些视图的引用。视图控制器是保存需要修改的视图定义的 outlet 的好地方。使用清单 4-1 所示的语法将您的outlet声明为属性。清单中的自定义类定义了两个 outlet (由 IBOutlet 关键字指定)和一个方法(指定 IBAction 返回类型)。outlet 拥有存储在故事板中一个按钮和一个文本字段的引用,而方法则响应按钮中的点击。
清单 4-1 在视图控制器类中定义 outlet 和方法
~~~
//Objective-C
@interface MyViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIButton *myButton;
@property (weak, nonatomic) IBOutlet UITextField *myTextField;
- (IBAction)myButtonAction:(id)sender;
@end
//Swift
class MyViewController: UIViewController {
@IBOutlet weak var myButton : UIButton!
@IBOutlet weak var myTextField : UITextField!
@IBAction func myButtonAction(sender: id)
}
~~~
在故事板中,记得将视图控制器的 outlet 和 action 连接到相应的视图。在故事板文件中连接 outlet 和 action 可以确保在加载视图时配置它们。有关如何在 Interface Builder 中创建 outlet 和 action 连接的信息,请参阅 Interface Builder Connections Help 。有关如何处理应用程序中的事件的信息,请参见 Event Handling Guide for iOS 。
### 在运行时显示视图
故事板使装载和显示视图控制器视图的过程非常简单。当需要时,UIKit自动从你的故事板文件中载入视图。作为加载过程的一部分,UIKit执行以下一系列任务:
1. 使用您的故事板文件中的信息实例化视图。
2. 连接所有的 outlet 和 action 。
3. 将根视图分配给视图控制器的视图属性。
4. 调用视图控制器的 awakeFromNib 方法。
5. 当这个方法被调用时,视图控制器的特征集合是空的,视图可能不在它们的最终位置。
6. 调用视图控制器的 viewDidLoad 方法。
7. 使用该方法添加或删除视图,修改布局约束,并为视图加载数据。
在屏幕上显示视图控制器的视图之前,UIKit为您提供了一些额外的机会在屏幕前后准备这些视图。 具体来说,UIKit执行以下任务序列:
1. 调用视图控制器的 viewWillAppear: 方法让它知道它的视图将会出现在屏幕上。
2. 更新视图的布局。
3. 屏幕上显示的视图。
4. 视图在屏幕上时调用 viewDidAppear: 方法。
添加,删除或修改视图的大小或位置时,请记住添加和删除适用于这些视图的任何约束。在下一个更新周期中,布局引擎使用当前的布局约束计算视图的大小和位置,并将这些更改应用到视图层次结构中。
有关如何在不使用故事板的情况下创建视图的信息,请参阅 [UIViewController Class Reference](https://developer.apple.com/documentation/uikit/uiviewcontroller) 。
### 管理视图布局
当视图的大小和位置发生变化时,UIKit将更新视图层次结构的布局信息。对于使用自动布局配置的视图,UIKit会使用自动布局引擎,并根据当前的约束来更新布局。UIKit还允许其他关注布局变动的对象,比如使用中的 presentation controller,知道布局的变化,这样它们就可以做出相应的响应。
在布局过程中,UIKit会在几个点通知你,这样你就可以执行其他的与布局相关的任务。使用这些通知来修改布局约束,或者在布局约束应用之后对布局进行最后的调整。在布局过程中,UIKit为每个受影响的视图控制器做如下操作:
1. 根据需要更新视图控制器及其视图的特征集合,参考 [When Do Trait and Size Changes Happen?](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/TheAdaptiveModel.html#//apple_ref/doc/uid/TP40007457-CH19-SW6)
2. 调用视图控制器的 `- (void)viewWillLayoutSubviews;` 方法。
3. 调用当前UIPresentationController对象的 `- (void)containerViewWillLayoutSubviews;` 方法。
4. 调用视图控制器根视图的`- (void)layoutSubviews;` 方法。该方法的默认实现使用可用的约束来计算新的布局信息。然后该方法遍历视图层次结构,并为每个子视图调用`- (void)layoutSubviews;` 方法。
5. 将计算的布局信息应用于视图。
6. 调用视图控制器的 `- (void)viewDidLayoutSubviews;` 方法。
7. 调用当前 UIPresentationController 对象的 `- (void)containerViewDidLayoutSubviews;` 方法。
视图控制器可以使用 `- (void)viewWillLayoutSubviews; 和 - (void)viewDidLayoutSubviews` 方法来执行可能影响布局过程的额外更新。在布局之前,您可以添加或删除视图,更新视图的大小或位置,更新约束,或更新其他与视图相关的属性。在布局之后,您可以重新加载表数据,更新其他视图的内容,或者对视图的大小和位置进行最后的调整。
下面是一些有效管理布局的技巧:
* **使用自动布局**。使用自动布局创建的约束是一种灵活而简单的方法,可以将内容放置在不同的屏幕大小上。
* **利用 topLayoutGuide 和 bottomLayoutGuide** 。这样可以保证你的展示内容可见。topLayoutGuide 位置会根据状态栏和导航栏高度调整,bottomLayoutGuide 位置根据底部的工具栏或者菜单栏调整。
* **记得在添加或删除视图时更新约束**。如果您动态添加或删除视图,请记住更新相应的约束。
* **在视图控制器的视图做动画时,暂时移除约束**。当使用 UIKit Core Animation 制作动画时,在动画的持续时间内移除你的约束,并在动画结束时将它们添加回来。如果您的视图的位置或大小在动画中发生了更改,请记住更新您的约束。
关于展示控制器的信息和它们在视图控制器体系结构中所扮演的角色,请参阅 [The Presentation and Transition Process](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PresentingaViewController.html#//apple_ref/doc/uid/TP40007457-CH14-SW7) 。
### 有效地管理内存
尽管内存分配的大部分方面都由您来决定,但是表 4-1 列出了您最有可能分配或释放内存的 UIViewController 的方法。 大多数释放伴随着删除对象的强引用。 要删除对象的强引用,请将指向该对象的属性和变量设置为 nil 。
| 任务 | 方法 | 讨论 |
| --- | --- | --- |
| 创建视图控制器所需的关键数据结构 | Initialization 初始化方法 | 您的自定义初始化方法(无论它是否被命名为init或其他形式)总是负责将您的视图控制器对象放入一个已知的良好状态。使用这些方法来分配所需的数据结构以确保正确的操作。 |
| 分配或加载要在您的视图中显示的数据 | `- (void)viewDidLoad;` | 使用 viewDidLoad 方法来加载您想要显示的任何数据对象。当调用这个方法时,您的视图对象将被确保存在并且处于一个已知的良好状态。 |
| 回应低内存通知 | `- (void)didReceiveMemoryWarning;` | 使用该方法来释放与您的视图控制器相关联的所有非关键对象。释放尽可能多的内存。 |
| 释放视图控制器所需的关键数据结构。 | `- (void)dealloc;` | 重写这个方法,只在最后时刻对您的视图控制器类进行清理。系统会自动释放存储在实例变量和类属性中的对象,因此您不需要显式地释放这些对象。 |