自定义ViewController
前言
概述
你可以继承UIViewController来展示的你的App的内容。大部分自定义的ViewController都是内容型的ViewController,也就是说,它包含了所有的视图,并负责为这些视图提供数据。相反,容器型ViewController不拥有所有的视图,它的部分子视图被其他ViewController管理着。自定义容器型ViewController和内容型ViewController的步骤基本一样,接下来我们会一一讲到。
对于内容型ViewController,常见的分类有:
- UITableViewController, 当你的ViewController的主View是一个表格时使用UITableViewController。
- UICollectionViewController,当你的ViewController的主View是一个集合性的视图。
- UIViewController,所有其他类型的视图。
对于容器类型的ViewController,父类取决于你是否修改了已存在的容器类。对于已存在的容器性视图控制器,选择一个你想要修改的,对于新的容器型ViewController,你可以子类化UIViewController。关于创建一个容器型ViewController,请参考 Implementing a Container View Controller
实现UI
你可以使用storyboard来实现ViewController的UI效果。虽然你也可以代码来实现UI布局,但是storyboard是将视图控制器的内容可视化并为不同的环境定制视图层次的绝佳方式。可视化的UI构建可以快速看到构建结果而不必编译和运行App。
下图展示了一个storyboad的例子,每个长方形区域表示一个ViewController和与之相关的视图。两个ViewController之间的箭头表示它们之间的关系和segues. 关系将容器型ViewController与它的子ViewController连接在一起。Segues 表示两个ViewController之间的转换。
每个新建的工程都有一个 main storyboard, 它通常已经包含一个或者多个ViewController。你可以在storyboard上从控件库中拖拽出一个新的ViewController添加到面板上。新建的ViewController初始状态下没有与之关联的类,必须通过设置“Identity inspector”给它赋予一个类。
使用storyboard编辑器可以做以下的操作:
- 为一个ViewController新增、编排、配置视图。
- 连接outlet和action,参考:Handling User Interactions。
- 创建ViewController之间的关系和segues。参考: Using Segues。
- 为不同的size classes 设置布局。
- 为手势来响应视图的交互操作。
如果没有使用过storyboard构建UI,你可以参考Start Developing iOS Apps Today这个教程来一步一步的学习。
处理用户交互
应用程序的响应者事件处理传入的事件并采取适当的操作。尽管ViewController是响应者对象,但它机会不直接处理事件,ViewController通常采用如下几种方式响应用户事件:
- ViewController定义action方法处理高级事件。Action 方法表示:
- 特定的动作。控件和视图调用指定action方法来响应特定的用户操作。
- 手势。手势识别器调用指定的方法来告知手势的当前状态。使用你自己的ViewController来处理手势状态的改变或者对整个手势做出响应。
- ViewController监听由系统或者其他对象发出的通知。上报通知变化是一种更新ViewController状态的一种方式。
- ViewController作为其他对象的数据源或者代理。ViewController经常用来作为table和collection view的数据源。你也可以用它作为一个对象的代理如“CLLocationManager”对象,它通过代理告知地理位置的变更。
响应事件经常涉及到更新视图的内容,这需要拥有要更新视图的引用。你可以在ViewController中定义要修改内容的视图的outlet。下面代码,自定义ViewController中包含两个outlets和一个action方法。
@interface MyViewController: UIViewController
@property (weak, nonatomic) IBOutlet UIButton *myButton;
@property (weak, nonatomic) IBOutlet UITextField *myTextField;
- (IBAction)myButtonAction:(id)sender;
@end
class MyViewController: UIViewController {
@IBOutlet weak var myButton : UIButton!
@IBOutlet weak var myTextField : UITextField!
@IBAction func myButtonAction(sender: id)
}
在你的storyboard中,记得将你的ViewController的outlets和action方法和相应的是视图相连接。在你的storyboard连接的outlets和actions确保他们在视图在被加载之后配置好。关于如何在Xib中创建outlet和action参考:Builder connections Help。 关于如何处理事件,参考:Event Hanlding Guide for iOS。
在运行时展示你的视图
Storyboard使得加载和展示ViewController的视图的流程非常简单。UIKit在它需要的使用视图的时候会自动从Storyboard中加载。作为加载流程的一部分,UIKit执行以下任务:
-
使用Storyboard中的信息实例化View
-
连接所有的outlets和actions
-
将根视图赋值给ViewController的view属性
-
调用ViewController的awakeFromNib
当这个方法被调用的时候,ViewController的特征集合还是空,视图可能不在他们最终的位置上。
-
调用ViewController的viewDidLoad方法
使用此方法添加或者移除视图,修改布局约束,加载视图所需要使用的数据。
在将ViewController的视图展示在屏幕之前,UIKit提供了一些额外的机会可以在视图展示到屏幕前后对视图进行更改。需要指出的是,下面方法将会被调用。
- 调用ViewController的viewWillApper:方法是你知道视图即将展示在屏幕上。
- 更新视图的layout。
- 将视图展示在屏幕上。
- 调用viewDidAppear方法当视图已经展示在屏幕上。
当你增加,移除或者修改视图或者视图的位置和大小,记得同时要修改相应视图上的约束。对视图的层级结构进行布局相关的更改会使得UIKit将layout标记为“dirty”。在下一次更新循环中,layout引擎会比较视图的大小和位置,使用当前的约束应用于改变的视图层级。
关于不适用Storyboard创建视图的方法,参考:UIViewController Class Referencep。
管理视图布局
当视图的尺寸和位置发生改变,UIKit会更新你视图的布局信息。UIKit使用自动布局引擎根据当前的约束更新视图。UIKit也可以让其他感兴趣的对象知道视图的变化,比如当前正在展示的对象。
在布局处理的过程中,UIKit通过几中方式通知你,你可以执行额外的与布局相关的操作。使用这些通知来更新你的布局约束或者在在布局约束应用之后做最终的调整。在布局期间,UIKit执行以下操作来影响ViewController:
-
在需要的时候更新ViewController和它的视图的特征集合。
-
调用viewWillLayoutSubviews方法。
-
调用当前UIPresentationController的containerViewWillLayoutSubviews方法。
-
调用ViewController的根视图的layoutSubViews方法。
该方法的默认实现是使用可用的约束计算新的布局。之后该方法调用所有子视图的layoutSubviews方法。
-
将计算后的布局信息应用到视图上。
-
调用ViewController的viewDidLayoutSubviews方法。
-
调用当前UIPresentationController对的containerViewDidLayoutSubviews方法。
ViewController可以使用viewWillLayoutSubviews和viewDidLayoutSubViews方法执行额外的更新操作来影响布局操作。在布局之前,你可以增加或者移除视图,改变视图的大小或者位置,更新约束或者更新相关视图的属性。布局之后,你可能会重新加载table的数据,更新视图的内容,或者对视图的大小和位置做出最后的调整。
这里有几个有效管理视图的提示:
- 使用Auto Layout。使用autolayout创建的约束可以方便的将内容在不同尺寸的屏幕上展示。
- 利用底部可底部的布局指南。使用这些布局指南可以确保内容始终可见。顶部布局指南影响着状态栏和导航栏的高度。同样的,底部的布局指南影响着tabbar和toolbar的高度。
- 记得在移除和添加视图时更新约束。如果你动态的增加和移除视图,记得更新响应的约束。
- ViewController的视图执行动画操作的时候暂时移除视图。当视图使用UIKit的CoreAnimation执行动画时,在动画执行期间移除约束,动画执行完成之后再将约束添加回来。还有如果在视图执行动画期间,如果改变了视图的位置和大小要更新约束。
有关展示控制器以及它在ViewController的结构中所扮演的角色,参考:The Presentation and Transition Process
高效管理内存
尽管内存分配的工作是由你来决定的,下表中列出了UIViewController关于内存创建和销毁的常用方法。大多数销毁操作时移除强引用的对象。可以将一个强引用对象置为nil来移除它。
任务 | 方法 | 描述 |
---|---|---|
创建ViewController要使用 的数据 |
initialization 方法 | 自定义初始化方法 (无论是用init命名还是其他名字), 它的职责就是将你的视图控制器置于 一个可用的良好状态。使用这些方法创建 相应的数据结构,以供对应的操作使用。 |
分配或者加载视图所使用的数据 | ViewDidLoad | 使用ViewDidLoad方法加载你打算展示的 数据。该方法调用时,你的视图对象已经 存在并且是可用状态。 |
响应系统低内存的通知 | didReceiveMemoryWaring | 使用此对象销毁ViewController中 非关键的数据,尽可能的销毁更对的数据 |
视图ViewController的关键对象 | dealloc | 重写该方法仅用于执行ViewController 的最后一次清理操作。系统会自动清理掉 实例变量所存储的变量和属性, 所以你不需要手动的释放。 |