前言
众所周知,咱们作为 iOS 开发者,素日的作业里做得最多的便是画 UI,写页面。既然要写页面,自然要知道视图显现的原理,这样才能写出更多功用的、功能更好的页面,所以这篇文章我会讲讲视图的显现和制作,以及它的坐标系特点。
UIView 与CALayer
UIView
UIView
是视图编程的根底,ta 指的是在屏幕上一块矩形的、办理”内容展现“的东西(比方图片、文字、按钮和视频),大多数情况下iOS 开发工程师都是跟 ta 打交道。它的作业责任是:
-
Drawing and animation制作和动画:能够用 UIKit 或许 Core Graphics 制作内容,也能够用简略的动画展现某个特点的改变
Core Graphics是一个绘图引擎,是 UIKit 的底层结构,它的 API 都是 C 语言的。常用于自定义制作
-
Layout and subview management布局和办理子视图
-
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。不慌,我为您找到了中文翻译。
但CALayer 不处理触摸事件,前面说了,触摸事件是 UIView 的任务。
两者的关系
CALayer 是 UIView 的一个特点,UIView 是 CALayer 的 delegate。UIView 是老板,它办理CALayer,它要负责创立CALayer、添加 CALayer,必要时销毁 CALayer
之所以要用两个东西来负责视图显现和呼应操作,很大的一个原因是「代码复用」。由于Apple 不仅开发了 iOS,还开发了 macOS,这两个体系在「视图怎么显现」这个问题上的解决方案很类似,可是「视图如何呼应用户操作」这个问题的解决上就有很大的差异,所以 Apple把前者的解决方案抽出来,做成 Core Animation,后者则是别离做成 UIKit 和 AppKit。这样一来,代码既能复用、在出现问题时也能够削减解决本钱。
一个View显现在屏幕上,需求走过怎样的流程?制作与布局
我简略阐述下步骤,其实只有前两个步骤是 iOS 开发者能够插手的当地:
- 布局:CPU 对一个 UIView进行 Frame 布局,设置它的layer 特点的特点(方位、布景色、边框等)。
-
显现(制作):layer 的可视内容开端制作。有两种办法能够设置这个显现内容:一是设置
layer.contents
。二是重写 UIView 的drawRect: 办法或许重写CALayerDelegate
的drawLayer:inContext:
办法。重写这两个办法,咱们称之为自定义制作了。注意,自定义制作会额定消耗 CPU 的功能,所以Apple 会建议咱们尽量不要自定义制作。 - 解码:Core Animation 结构预备发送数据到烘托服务。对图片进行解码,转换图片格式,图片要显现出来,必须从压缩状况转换到未解压状况,出于节约内存的意图,一般都是在制作前才去解码图片。
- 提交:把UIView 和 CALayer的层级关系进行打包,提交给烘托服务
- 生成帧缓存:依据图层数据生成前后帧缓存,参照VSync信号和CADisplayLink,切换前后帧缓存
- 烘托:将后帧缓存交给 GPU,最终显现到屏幕上。
咱们来重视一下能插手的当地
布局
layoutSubviews()
办法——布局子视图
依据已有的束缚,决定子视图的巨细和方位,它的触发时机如下:
- 修正 view 的巨细(调用view 的
layoutSubviews
) - 调用addSubview添加子视图时(调用子视图的
layoutSubviews
) - 在 UIScrollView 上翻滚(调用 UISCrollView 和ta 的父视图的
layoutSubviews
办法) - 更新view 的束缚时,(调用view 的
layoutSubviews
)
在什么时分需求重写这个办法?
当autoresizing 和基于束缚的布局不满足需求时,就重写这个办法。注意,重写这个办法是有功能损耗的,所以要尽量避免重写。
setNeedsLayout()
办法——符号是否需求从头布局
对某个 view 打上「脏符号」告知体系这个 view 需求从头布局,体系会在下一次runloop中调用layoutSubviews
进行布局。
layoutIfNeeded()
办法——立刻从头布局
告知体系立刻执行打了「脏符号」的layoutSubviews
,不用等下一次 runloop。
显现
drawRect:
办法——自定义制作
自定义制作,能够完成规范控件以外的显现效果。咱们平时用的都是体系自带的规范组件,比方 UILabel,UIImage等,能够设置他们的一些特点来完成需求,可是有的需求压根就不能通过这种办法来做,所以咱们需求把目光投向自定义制作,这儿是一个详细比如。
开发者不能手动调用这个办法。应该是在 runloop 的某个时刻节点,由体系调用。
setNeedsDisplay()
办法——符号谁谁谁需求自定义制作
打上「脏符号」,告知体系这个 view 需求从头制作。后续体系会调用这个 view 的drawRect
函数。
UIView 的体系制作和异步制作
当调用setNeedsDisplay
函数时,制作就开端了,它有两条路径,如下:
-
UIView 调用
setNeedsDisplay
后,调用本身 layer特点的setNeedsDisplay
,最终会调用 CALayer 的 display 函数,开端判断 layer 的 delegate 是否完成了displayLayer: 办法。 -
体系制作指的是默认的制作流程,在主线程生成 bitmap,把 bitmap 设置给 layer.contents。
- 异步制作指的是bitmap在子线程生成,生成了 bitmap 今后再回到主线程,把 bitmap 设置给 layer.contents。异步制作的好处是能减轻主线程的担负,提高界面的流畅性。
> Facebook 的[Texture](https://github.com/TextureGroup/Texture)和 ibireme 的[YYAsyncLayer](https://github.com/ibireme/YYAsyncLayer)都是依照这个思想完成的
总结:
- 假如想自定义布局,就重写
layoutSubviews
办法 - 假如想自定义制作,就重写
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);
}
这儿的解说是:修正后,关于 yellowView,父视图的原点坐标便是在左上角,再往上往左 50 的方位,所以yellowView 就也往左上角去了。解说参考
参考资料
- View Programming Guide for iOS:苹果文档
- CALayer Tutorial for iOS: Getting Started:展现了 CALayer 的强壮,中文翻译在此
- iOS 利用 drawRect 办法制作图形:自定义制作的比如
- 浅谈 UIView 的改写与制作
- iOS 保持界面流畅的技巧
- frame 与 bounds 的差异详解