聊聊ViewController

聊聊ViewController

本来想重新复习一下ViewController的加载过程的,后来发现要写的东西还是挺多了,看了很多资料,写了很多代码,当我发现控制器的转场动画的时候,感觉这坑太大了,还是以后专门抽个时间单独地去看看视图转场的动画吧~

层次图

首先是ViewController的层次结构图:

当然这个是最基本的层次图,在实际的运用中可以回涉及到大量的 viewController之间的交互,层级结构等。

加载顺序

在使用ViewController的时候,有一个常见的问题,就是它的方法加载的顺序,在这里主要分两种加载顺序,一个是使用code创建的ViewController,一个是IB创建的ViewController,前者顺序如下:

  1. convenience init
  2. super.init(nibName:, bundle:)
  3. loadView
  4. viewDidLoad
  5. viewWillAppear
  6. viewWillLayoutSubviews
  7. viewDidLayoutSubviews
  8. viewDidAppear

使用IB创建的ViewController,加载顺序如下(viewWillLayoutSubviews可能调用多次)

  1. init coder
  2. awakeFromNib
  3. loadView
  4. viewDidLoad
  5. viewWillAppear
  6. viewWillLayoutSubviews
  7. viewDidLayoutSubviews
  8. 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:

  1. Create a root view object. …
  2. 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.
  3. 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.
  4. Assign the root view to the view property of your view controller.

如果使用代码而不是storyboard创建Views的话,那么Apple建议重载VC的loadView(),在重载方法体内部需要添加什么呢?

  1. 创建根视图对象
  2. 创建子视图对象,且添加子视图对象到根视图对象上
  3. 添加autolayout设置约束(如果需要的话)
  4. 将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的方法中发生了什么呢?

  1. 如果关联了StoryBord或者XIB

    • 从storyboard / xib中加载视图
    • 将root view赋值给viewController的view属性
  2. 如果没关联StoryBord或者XIB

    • 创建一个UIView实例有一个默认的frame和autoResizingMask
    • 将实例赋值给viewController的view属性
  3. 根据StoryBord或者XIB中的 layout guides设置约束

  4. 调用viewDidLoad()

小总结

  1. 建议还是在loadView()中设置视图,不设置也行,你得明白视图初始化的过程中发生了什么
  2. 最好:在loadView中对viewController的view属性进行set;在viewDidLoad中只去get
  3. 以上只适用于UIViewController的子类,如果是子类的子类,仍然在viewdidload中进行视图的设置
4. 为什么layoutSubviews要调用多次?

如果你在storyboard上的控制器上加了一个控件,然后将storyboard上的这个控制器和一个自定义class关联起来,viewWillLayoutSubviews/viewDidLayoutSubviews会调用两次,为什么? 其一: 视图本身的根视图需要布局 其二: storyboard上添加的控件即根视图上的控件需要布局 所以,当我将storyboard上的控件删除之后,整个启动过程中viewWillLayoutSubviews/viewDidLayoutSubviews只会调用一次

那么有哪些因素会导致layoutview要调用呢?

  1. viewController的view属性的bounds发生了改变
  2. 添加子视图会发生调用此方法
  3. 旋转屏幕会调用此方法

Segue的行为

在使用StoryBoard创建视图的时候,segue的行为会发生什么呢?

  • shouldPerformSegue 方法可以阻止segue的行为
  • prepareForSegue:方法可以将数据从源视图控制器传递到目标控制器

参考文章

View Controller Programming Guide for iOS 中文版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值