前言

众所周知,咱们作为 iOS 开发者,素日的作业里做得最多的便是画 UI,写页面。既然要写页面,自然要知道视图显现的原理,这样才能写出更多功用的、功能更好的页面,所以这篇文章我会讲讲视图的显现和制作,以及它的坐标系特点。

UIView 与CALayer

UIView

UIView 是视图编程的根底,ta 指的是在屏幕上一块矩形的、办理”内容展现“的东西(比方图片、文字、按钮和视频),大多数情况下iOS 开发工程师都是跟 ta 打交道。它的作业责任是:

  1. Drawing and animation制作和动画:能够用 UIKit 或许 Core Graphics 制作内容,也能够用简略的动画展现某个特点的改变

    Core Graphics是一个绘图引擎,是 UIKit 的底层结构,它的 API 都是 C 语言的。常用于自定义制作

  2. Layout and subview management布局和办理子视图

  3. Event handling处理触摸事件:由于 UIView 是UIResponder的子类,一起它也能通过设置gesture recognizer来呼应特定的手势动作。

UIView 其实不是实际负责显现的那个,它有一个非常重要的特点—— CALayer

@interface UIView : UIResponder <procotols>
@property(nonatomic,readonly,strong) CALayer *layer;      
// returns view's layer. Will always return a non-nil value. view is layer's delegate

CALayer

CALayer 不属于 UIKit,它属于 Core Animation,它负责“可视内容的展现”。也便是说,UIView 假如没有CALayer,那么它是不能够展现内容的。反过来说,假如你需求显现一个东西,一起又不需求 ta 呼应用户操作,那其实你能够只用 CALayer就能完成该功用。它是轻量级的 UIView。


一个 CALayer要展现可视内容,所以它有一个特点contents,这指向一块缓存区,称之为backing store,backing store里放的是位图bitmap。

bitmap便是要显现到屏幕上的东西

手机屏幕改写时,会取这个 bitmap,出现到屏幕上。


CALayer 是个强壮的工具,它能设置显现内容 content 的特点(contentGravity、布景色彩、边框、圆角),能播映视频,能设置翻滚效果,能设置文字,能设置渐变布景色彩,能完成粒子喷射效果。。。总归便是公鸡中的战斗机。它的详细功用展现能够看看老外Ron Kliffer写的文章:CALayer Tutorial for iOS: Getting Started。不慌,我为您找到了中文翻译。

iOS 界面开发 1—— 画 UI 时你应该记在心里的知识
但CALayer 不处理触摸事件,前面说了,触摸事件是 UIView 的任务。

两者的关系

CALayer 是 UIView 的一个特点,UIView 是 CALayer 的 delegate。UIView 是老板,它办理CALayer,它要负责创立CALayer、添加 CALayer,必要时销毁 CALayer

iOS 界面开发 1—— 画 UI 时你应该记在心里的知识

之所以要用两个东西来负责视图显现和呼应操作,很大的一个原因是「代码复用」。由于Apple 不仅开发了 iOS,还开发了 macOS,这两个体系在「视图怎么显现」这个问题上的解决方案很类似,可是「视图如何呼应用户操作」这个问题的解决上就有很大的差异,所以 Apple把前者的解决方案抽出来,做成 Core Animation,后者则是别离做成 UIKit 和 AppKit。这样一来,代码既能复用、在出现问题时也能够削减解决本钱。

一个View显现在屏幕上,需求走过怎样的流程?制作与布局

我简略阐述下步骤,其实只有前两个步骤是 iOS 开发者能够插手的当地:

  1. 布局:CPU 对一个 UIView进行 Frame 布局,设置它的layer 特点的特点(方位、布景色、边框等)。
  2. 显现(制作):layer 的可视内容开端制作。有两种办法能够设置这个显现内容:一是设置 layer.contents。二是重写 UIView 的drawRect: 办法或许重写CALayerDelegatedrawLayer:inContext: 办法。重写这两个办法,咱们称之为自定义制作了。注意,自定义制作会额定消耗 CPU 的功能,所以Apple 会建议咱们尽量不要自定义制作。
  3. 解码:Core Animation 结构预备发送数据到烘托服务。对图片进行解码,转换图片格式,图片要显现出来,必须从压缩状况转换到未解压状况,出于节约内存的意图,一般都是在制作前才去解码图片。
  4. 提交:把UIView 和 CALayer的层级关系进行打包,提交给烘托服务
  5. 生成帧缓存:依据图层数据生成前后帧缓存,参照VSync信号和CADisplayLink,切换前后帧缓存
  6. 烘托:将后帧缓存交给 GPU,最终显现到屏幕上。

咱们来重视一下能插手的当地

布局

layoutSubviews() 办法——布局子视图

依据已有的束缚,决定子视图的巨细和方位,它的触发时机如下:

  1. 修正 view 的巨细(调用view 的layoutSubviews
  2. 调用addSubview添加子视图时(调用子视图的layoutSubviews
  3. 在 UIScrollView 上翻滚(调用 UISCrollView 和ta 的父视图的layoutSubviews 办法)
  4. 更新view 的束缚时,(调用view 的layoutSubviews

在什么时分需求重写这个办法?

当autoresizing 和基于束缚的布局不满足需求时,就重写这个办法。注意,重写这个办法是有功能损耗的,所以要尽量避免重写。


setNeedsLayout() 办法——符号是否需求从头布局

对某个 view 打上「脏符号」告知体系这个 view 需求从头布局,体系会在下一次runloop中调用layoutSubviews 进行布局。


layoutIfNeeded() 办法——立刻从头布局

告知体系立刻执行打了「脏符号」的layoutSubviews,不用等下一次 runloop。

显现

drawRect: 办法——自定义制作

自定义制作,能够完成规范控件以外的显现效果。咱们平时用的都是体系自带的规范组件,比方 UILabel,UIImage等,能够设置他们的一些特点来完成需求,可是有的需求压根就不能通过这种办法来做,所以咱们需求把目光投向自定义制作,这儿是一个详细比如。

开发者不能手动调用这个办法。应该是在 runloop 的某个时刻节点,由体系调用。


setNeedsDisplay() 办法——符号谁谁谁需求自定义制作

打上「脏符号」,告知体系这个 view 需求从头制作。后续体系会调用这个 view 的drawRect函数。

UIView 的体系制作和异步制作

当调用setNeedsDisplay 函数时,制作就开端了,它有两条路径,如下:

iOS 界面开发 1—— 画 UI 时你应该记在心里的知识

  • UIView 调用 setNeedsDisplay后,调用本身 layer特点的 setNeedsDisplay ,最终会调用 CALayer 的 display 函数,开端判断 layer 的 delegate 是否完成了displayLayer: 办法。

  • 体系制作指的是默认的制作流程,在主线程生成 bitmap,把 bitmap 设置给 layer.contents。

iOS 界面开发 1—— 画 UI 时你应该记在心里的知识

  • 异步制作指的是bitmap在子线程生成,生成了 bitmap 今后再回到主线程,把 bitmap 设置给 layer.contents。异步制作的好处是能减轻主线程的担负,提高界面的流畅性

iOS 界面开发 1—— 画 UI 时你应该记在心里的知识

> Facebook 的[Texture](https://github.com/TextureGroup/Texture)和 ibireme 的[YYAsyncLayer](https://github.com/ibireme/YYAsyncLayer)都是依照这个思想完成的

总结:

  1. 假如想自定义布局,就重写layoutSubviews 办法
  2. 假如想自定义制作,就重写 drawRect 办法

视图的坐标系以及特点frame, bounds 和 center

在坐标系中,有几个结构咱们常常用到:

结构 效果
CGPoint 表明方位,包括 x,y 两个特点
CGSize 表明尺度,包括 width 和 height 两个特点
CGRect 表明屏幕上的一块矩形区域,包括 origin 特点(CGPoint 类型,表明矩形左上角)和size 特点(CGSize 类型)

UIView 中的 frame、bounds、center特点都能在CALayer 找到对应的特点,如下

UIView CALayer 效果
frame frame 指定了view巨细,和view在父视图坐标系中的方位。一般用于修正 view 的巨细和方位
center position view 中心点在父视图坐标系中的方位,用于修正 view 的方位
bounds bounds 指定了view巨细,和在本身坐标系中的方位。常用于制作,修正 bounds 会影响子视图的方位。
transform affineTransform 用于仿射改换,也便是对 View 进行旋转、缩放、移动等操作
anchorPoint 锚点,CGPoint类型,表明一个相对方位,左上角为(0,0), 右下角为(1,1), anchorPoint 的默认值为(0.5, 0.5)。一般用作 transform 仿射改换时的原点

只改方位的时分,最好用 center 特点,由于 center 特点永远是有用的,即便view 缩放和或许旋转了。而 frame 就不必定了,当视图的改换不等于单位的改换时(if the view’s transform is not equal to the identity transform),它便是无效的。详见View Programming Guide for iOS

一个反直觉的设置是,当咱们把父视图的 bounds 从(0,0, 200, 200) 修正为(50, 50 , 200 , 200)时,它的子视图的方位会往左上角移动,代码如下:

- (void)viewDidLoad {
   [super viewDidLoad];
  // Do any additional setup after loading the view.
   [self.view setBackgroundColor:UIColor.whiteColor];
  
  UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
   [blueView setBackgroundColor:UIColor.blueColor];
   [self.view addSubview:blueView];
  UITapGestureRecognizer *ge = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClick)];
   [blueView addGestureRecognizer:ge];
  
  UIView *yellowView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
   [yellowView setBackgroundColor:UIColor.yellowColor];
  
   [blueView addSubview:yellowView];
  
  self.blueView = blueView;
  self.yellowView = yellowView;
}
​
- (void)onClick {
  self.blueView.bounds = CGRectMake(50, 50, 200, 200);
}

iOS 界面开发 1—— 画 UI 时你应该记在心里的知识

iOS 界面开发 1—— 画 UI 时你应该记在心里的知识

这儿的解说是:修正后,关于 yellowView,父视图的原点坐标便是在左上角,再往上往左 50 的方位,所以yellowView 就也往左上角去了。解说参考

参考资料

  • View Programming Guide for iOS:苹果文档
  • CALayer Tutorial for iOS: Getting Started:展现了 CALayer 的强壮,中文翻译在此
  • iOS 利用 drawRect 办法制作图形:自定义制作的比如
  • 浅谈 UIView 的改写与制作
  • iOS 保持界面流畅的技巧
  • frame 与 bounds 的差异详解