聊聊ViewController
本来想重新复习一下ViewController的加载过程的,后来发现要写的东西还是挺多了,看了很多资料,写了很多代码,当我发现控制器的转场动画的时候,感觉这坑太大了,还是以后专门抽个时间单独地去看看视图转场的动画吧~
层次图
首先是ViewController的层次结构图:
viewController
之间的交互,层级结构等。
加载顺序
在使用ViewController的时候,有一个常见的问题,就是它的方法加载的顺序,在这里主要分两种加载顺序,一个是使用code创建的ViewController,一个是IB创建的ViewController,前者顺序如下:
- convenience init
- super.init(nibName:, bundle:)
- loadView
- viewDidLoad
- viewWillAppear
- viewWillLayoutSubviews
- viewDidLayoutSubviews
- viewDidAppear
使用IB创建的ViewController,加载顺序如下(viewWillLayoutSubviews可能调用多次)
- init coder
- awakeFromNib
- loadView
- viewDidLoad
- viewWillAppear
- viewWillLayoutSubviews
- viewDidLayoutSubviews
- viewDidAppear
针对这个不同的加载顺序,有几个点要拿出来讲一讲:
1. 为什么代码初始化需要调用父类的init(nibName:, bundle:)?
The designated initializer. If you subclass UIViewController, you must call the super implementation of this method, even if you aren't using a NIB. (As a convenience, the default init method will do this for you, and specify nil for both of this methods arguments.) 在Apple的注释中写道,如果你创建UIViewController的话(即使没有使用NIB),一定要在自定义的初始化方法中写上父类的实现即:
init() {
super.init(nibName: nil, bundle: nil)
}
复制代码
即使没有convenient init()方法,默认的init方法也会为其调用父类的init(nibName: nil, bundle: nil)方法
2. 两种创建ViewController实例加载的区别在哪里?
使用IB创建的viewController手动调用init(nibName: nil, bundle: nil)时,当然会调用这个方法,如果没有呢?即1. 用IB自动跳转,2. 或者使用代码用segue
跳转,3. 或者使用代码的instantiateViewController(withIdentifier:)
这三种方式创建,都不会调用init(nibName:, bundle:)
方法,而是调用init(coder:)
方法
使用IB创建的ViewController一定会调用调用
init(coder:)
方法
3. loadView()
VS viewDidLoad()
在View Controller Programming Guide for iOS)中:
If you prefer to create views programmatically, instead of using a storyboard, you do so by overriding your view controller’s loadView method. Your implementation of this method should do the following:
- Create a root view object. …
- Create additional subviews and add them to the root view. For each view, you should:
- Create and initialize the view.
- Add the view to a parent view using the addSubview: method.
- If you are using auto layout, assign sufficient constraints to each of the views you just created to control the position and size of your views.
- Assign the root view to the view property of your view controller.
如果使用代码而不是storyboard创建Views的话,那么Apple建议重载VC的loadView(),在重载方法体内部需要添加什么呢?
- 创建根视图对象
- 创建子视图对象,且添加子视图对象到根视图对象上
- 添加autolayout设置约束(如果需要的话)
- 将root view赋值给vc的view属性
上面是针对纯代码创建views,如果是使用IB创建呢?
If you cannot define your views in a storyboard or a nib file, override the loadView method to manually instantiate a view hierarchy and assign it to the view property. … You can override [the loadView] method in order to create your views manually. If you choose to do so, assign the root view of your view hierarchy to the view property. … If you want to perform any additional initialization of your views, do so in the viewDidLoad method.
也就是说Apple,其实是推荐在loadView中对视图层次结构中进行设置的,而不是在ViewDidLoad()中,但是我们通常是在ViewDidLoad()中对views进行设置的,但是这也是安全的,因为loadView()调用结束之后,就立马调用了ViewDidLoad(),虽然不合苹果规范,但是也是可行的!
那么在loadView的方法中发生了什么呢?
-
如果关联了StoryBord或者XIB
- 从storyboard / xib中加载视图
- 将root view赋值给viewController的view属性
-
如果没关联StoryBord或者XIB
- 创建一个UIView实例有一个默认的frame和autoResizingMask
- 将实例赋值给viewController的view属性
-
根据StoryBord或者XIB中的 layout guides设置约束
-
调用viewDidLoad()
小总结
- 建议还是在loadView()中设置视图,不设置也行,你得明白视图初始化的过程中发生了什么
- 最好:在loadView中对viewController的view属性进行set;在viewDidLoad中只去get
- 以上只适用于UIViewController的子类,如果是子类的子类,仍然在viewdidload中进行视图的设置
4. 为什么layoutSubviews要调用多次?
如果你在storyboard上的控制器上加了一个控件,然后将storyboard上的这个控制器和一个自定义class关联起来,viewWillLayoutSubviews/viewDidLayoutSubviews
会调用两次,为什么? 其一: 视图本身的根视图需要布局 其二: storyboard上添加的控件即根视图上的控件需要布局 所以,当我将storyboard上的控件删除之后,整个启动过程中viewWillLayoutSubviews/viewDidLayoutSubviews
只会调用一次
那么有哪些因素会导致layoutview要调用呢?
- viewController的view属性的bounds发生了改变
- 添加子视图会发生调用此方法
- 旋转屏幕会调用此方法
Segue的行为
在使用StoryBoard创建视图的时候,segue的行为会发生什么呢?
- shouldPerformSegue 方法可以阻止segue的行为
- prepareForSegue:方法可以将数据从源视图控制器传递到目标控制器