iOS UIView绘制(二)View Hierarchy

status
category
date
summary
slug
icon
tags
password
本文主要围绕UIView从创建、添加到父视图、最后从父视图移除的相关过程,介绍UIViewUIViewControllerUIWindow、以及UIView自身的关系。同是介绍生命周期,和上一篇有些重复,但上一篇主要是对UIView从初始化到渲染整体流程进行概括,这一篇仅仅围绕初始化进行,不涉及布局和渲染,并且结合demo分析了开发中常见的问题。

对象的创建

这里的UIView创建指的是我们需要添加到UIViewController中的视图,UIViewControllerRoot View,也就是我们常调用的self.view不需要我们创建,由UIViewController自动创建。

构造方法

创建一个UIView,第一步是调用构造方法,究竟调用哪个方法,取决于是从代码创建,还是使用了Interface Builder
  • 通过纯代码创建一个UIView对象,并初始化它的frame
  • 如果使用了xib或者storyboard,初始化就不会调用上面的方法,而是另一个方法:
虽说两个方法都是“初始化”,但工作并不一样:通过纯代码创建一个UIView对象,就真真正正只是创建了一个对象。但使用了xib或者storyboard时,整个逆序列化的过程不仅仅是初始化对象,而是做了所有代码该做的事情,例如设置UIView相关属性、建立View树调整View层级等等。

自定义UIView

对于自定义的UIView,需要同时实现两个构造方法,所以需要初始化的相关操作可以这样写:
另外,不要使用awakeFromNib进行相关属性的初始化,先看看苹果的官方文档:
Note
During Interface Builder’s test mode, this message is also sent to objects instantiated from loaded Interface Builder plug-ins. Because plug-ins link against the framework containing the object definition code, Interface Builder is able to call their awakeFromNibmethod when present. The same is not true for custom objects that you create for your Xcode projects. Interface Builder knows only about the defined outlets and actions of those objects; it does not have access to the actual code for them.
Important
Because the order in which objects are instantiated from an archive is not guaranteed, your initialization methods should not send messages to other objects in the hierarchy. Messages to other objects can be sent safely from within an awakeFromNib method.
文档中明确说明,同一归档中的所有对象初始化完成之后才会调用awakeFromNib方法,并且对象初始化的顺序是不固定的,所以awakeFromNib的调用顺序和View的父子关系并不能对应,导致这种方式出现问题。

构建View Hierarchy

创建完UIView,设置完相关属性,一般下一步就是self.view.addSubview()。子View可以通过superview获取父View,父View可以通过view.subviews获取一个UIView数组,这个数组是按顺序排列的,越靠前的UIView越靠后(可以理解为Z轴越小)。addSubview()会把子View添加到数组末尾,因此如果有重叠的话,后面添加的View会遮盖之前的View。当然也可以通过insertSubview来直接选择插入的位置,可以指定index,或指定上下层的View。
这一步是在构建View树,也就是View的层级,这是UIView和其他UIView的关系。实际上UIViewControllerUIView的关系,都是通过UIViewController的根视图,也就是我们常用的self.view关联起来的。UIViewController持有根视图,根视图又管理者所有子视图。除了响应链,一个UIView对于它所在的UIViewController是没有感知的。

联动UIWindow

UIWindow是一种特殊的UIView,iOS程序启动后创建的第一个视图就是UIWindow,然后是UIViewController的根View,接下来把这个根View添加为UIWindow的子View上。因为self.view.addSubview的操作,创建了View层级树,UIViewController包含的所有View也通过Root ViewUIWindow有了联系。这个过程可以用下图来表示:
notion image
新创建一个iOS工程时,默认会执行以下操作:
  1. 创建UIWindow:自动创建一个UIWindow,即自动创建的AppDelegate中的window属性
  1. 创建UIViewController:自动创建Main.storyboard,将展示的第一个UIViewController指定为自动创建的ViewController,即storyboard中箭头指向的
  1. 绑定:把上面创建的ViewController指定为UIWindowRoot ViewController,同时Root ViewControllerRoot View成为UIWindow的子View
因为使用了storyboard,这个过程被隐藏了:从Main函数执行,到创建Application,到设置AppDelegate,再到执行上述步骤,最后调用didFinishLaunchingWithOptions。整个过程变得不可见,也方便了很多,实际上可以用代码这样表示:
为了便于理解这几个对象之间的关系,我画了一张图:
notion image
切记UIWindow也是UIViewview.window获取的就是View树的根节点。也就是说,View的添加可以不经过ViewController,但推荐使用上面这种方式:通过self.window?.rootViewController = viewControllerUIWindowUIViewController绑定起来。
一般情况下,不要使用另一种方式:self.window?.addSubview(view)这种方式越过了ViewControllerview不再被ViewController管理,而是直接和window发生关系,可能引发一些问题:
  • 当view产生事件,通知控制器的时候,控制器可能已经销毁
  • 发生旋转屏幕等事件时,事件传递:UIApplication --> UIWindow --> Root ViewController --> View,如果没有了ViewControllerView就收不到消息,不能响应事件

总结

本文介绍了UIView创建的过程,以及这个过程中相关对象的行为,例如UIViewControllerUIWindow,对应的方法也比较明了:
  1. 对象的初始化:主要是构造方法init
  1. 构建View树:父View的addSubview和子View的willMove(toSuperview newSuperview: UIView?)didMoveToSuperview
  1. 联动UIWindow:View的willMove(toWindow newWindow: UIWindow?)didMoveToWindow
Loading...

© 刘口子 2018-2025