UIView学习笔记

来源:互联网 发布:模拟登陆知乎 编辑:程序博客网 时间:2024/05/18 18:21

10th,May,2016

前言

UIView职责:

(1). 绘制和动画

(2). 布局和子视图管理

(3). 事件处理


绘制和动画

绘制像素到屏幕上

ps: 该部分内容主要整理自 绘制像素到屏幕上
为了将像素显示在屏幕上,一些处理将在CPU上进行。然后数据将会传送到GPU(即从RAM移动到VRAM),这也需要做一些相应的操作,最终像素显示在屏幕上。
比如, 让CPU从bundle加载一张PNG图片并解压,将解压后的图片作为一个纹理上传到GPU并显示。
纹理复用规则: 同样的纹理可被复用,CPU告诉GPU新的位置,GPU就可以重用存在的纹理。CPU无需重新渲染文本。
为了达到每秒60次的速度刷新,CPU和GPU不能过载,在没用尽GPU资源时,建议尽可能多的把绘制工作交给GPU,让CPU尽可能的执行应用程序。可使用OpenGL ES Driver instrument点击小的i按钮,配置勾选Device Utilization %,查看GPU的负荷。 
iOS中,几乎所有的东西都是通过Core Animation绘制。(OS X中,也有部分直接使用Core Graphics绘制)

不透明: 可节省GPU很大的工作量,只需简单的拷贝而不需要合成所有的像素值。其中,图片没有alpha通道和图片每个地方的alpha值是100%,将会产生很大的不同。

离屏渲染(OffScreen Rendering):

 屏幕外的渲染会合并/渲染图层树的一部分到新的缓冲区,然后该缓冲区被渲染到屏幕上。离屏渲染合成计算非常昂贵:
1)需创建新缓冲区; 2) 上下文切换: 需多次切换上下文环境
离屏渲染触发方式:
1) shouldRasterize: 设置成YES,在触发离屏绘制的同时,会将光栅化后的内容缓存,如对应的layer及sublayers没有发生改变,则可复用。能很大程度提升渲染性能 
2) masks(遮罩)
3) shadows
4) edge antialiasing
5) group opacity(不透明)

缺点:
很容易造成性能损耗
工具: Instrument的Core Animation工具有一个叫做Color OffScreen-Rendered Yellow的选项,它将已经渲染到屏幕外缓冲区的区域标注为黄色。同时检查Color Hits Green and Misses Red选项。绿色代表无论何时一个屏幕外缓冲区被复用,红色代表缓冲区被重新创建。
       ps: iOS9.0之前UIImageView,UIButton设置圆角会触发离屏渲染,iOS9.0之后UIButton设置圆角会触发离屏渲染,而UIImageView中 png图片设置圆角则不会触发离屏渲染。
       建议:尽量避免离屏渲染。同时要注意,rasterized layer的空间是有限的,大概只有屏幕大小两倍的空间存储rasterized layer/屏幕缓冲区。

相关概念

GPU: 一个专门为图形高并发计算而量身定做的处理单元。一个非常强大的图形硬件,在显示像素方面起着核心作用. 渲染性能比CPU高效很多  VRAM
OpenGL(Open Graphics Library): 一个提供2D和3D图形渲染的API。第一个和图形硬件(GPU)交流的标准化方式. RAM
OpenGL ES : 将涂层合并,显示到屏幕上
Core Animation: 判断哪些图层需要被(重新)绘制
Core Graphics
Quartz 2D: 一个低级且基于C的强大的2D绘制API。 有基于路径的绘制,反锯齿渲染, 透明涂层,分辨率,并且设备独立。
合成: 所有的纹理以某种方式堆叠在彼此的顶部,创建最终在屏幕上显示的图像。对于屏幕上的每个像素,GPU需要计算RGB的值。公式为: R = S + D * (1 - Sa) 结果颜色= 源色+目标色*(1-源色透明度)
mask: 一个拥有alpha的位图,一个图层可以有一个和它相关联的mask。

初始化方法Method to override

- initWithFrame:  从代码加载视图

- initWithCoder:  从xib文件加载视图.(先调用initWithCoder,然后发送-awakeFromNib消息给nib中的每个对象)

- layerClass:if you want your view to use a different Core Animation layer for its backing store.


awakeFromNib 从xib或者storyboard加载完毕就会调用,当.nib文件被加载的时候,会发发送awakeFromNib的消息给nib文件中的每个对象,每个对象都可以重写awakeFromNib响应该消息。

frame , bounds 与 center

(1). frame: 当前视图在父视图中的位置和大小

(2). bounds: 当前视图在自身坐标系统中的位置和大小

(3).  center: 当前视图的中心店在父视图中的位置

一般来说,通过frame属性设置视图的大小和位置,使用center或者frame改变视图的位置。

- (CGRect)frame {

return CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height)

}

- (CGRect)bounds {

return CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)

}

观察视图相关的变化Observing View-related Changes


didAddSubview: 该方法的默认实现中does nothing. 子类可以重写该方法实现在子视图被添加的时候实现额外的操作。response to adding a subview using any of the relevant view methods
willRemoveSubviewdefault implementation of this method does nothing. 当子视图的父视图修改或者子视图从视图层次结构中被移除时会被调用。
willMoveToSuperview: 同上,does nothing。its superview is about to change to the specified superview. 当视图的父视图改成指定的视图时调用。[子视图将要被添加到另一个视图的时候发送此消息]
didMoveToSuperview: default implementation does nothing .  its superview changed.
willMoveToWindow: its window object is about to change. [子视图将要被添加到window的时候发送此消息]  
didMoveToWindow

   The window property may be nil by the time that this method is called, indicating that the receiver does not currently reside in any window. This occurs when the receiver has just been removed from its superview or when the receiver has just been added to a superview that is not attached to a window. Overrides of this method may choose to ignore such cases if they are not of interest.

弹出键盘: willMoveToWindow -> willMoveToSuperview -> didMoveToWindow -> didMoveToSuperview 

return隐藏键盘: willMoveToSuperview -> willMoveToWindow -> didMoveToWindow -> didMoveToSuperview

Q: willMoveToWindwo与willMoveToSuperview两者之间有什么区别呢

布局和子视图管理

setNeedsLayout 与 setNeedsDisplay

1) setNeedsLayout: 可让视图主动更新布局,默认调用layoutSubViews,可以处理子视图中的一些数据
2) setNeedsDisplay/ setNeedsDisplayInRect:  异步执行, 会调用drawRect,就可以拿到UIGraphicsGetCurrentContext就可以画画了

ps: 该函数为图层设置了一个标识,显示的还是原来的内容。(所以多次调用该函数并不会造成性能损失)

渲染系统准备好,图层会装配它的后备存储。 然后建立一个Core Graphics上下文(CGContextRef),将后备存储对应内存中的数据恢复出来,绘图会进入对应的内存区域,并使用 CGContextRef绘制。

CGContextref使用方法: UIKit将后备存储的CGContextRef推进graphics context stack,UIGraphicsGetCurrent()会返回对应的上下文,绘图将会进入涂层的后备存储。

layoutSubviews被调用的情况:

1) . addSubview会出发layoutSubviews,init初始化不会调用layoutSubviews;

2). 修改view的frame会出发layoutSubviews;

3). 滚动UIScrollView会出发layoutSubviews;

4). 旋转会出发父UIView的layoutSubviews事件; (?2. 子UIView的layoutSubviews事件为什么不会触发)

5). 改变UIView大小也会触发父UIView的layoutSubviews事件;

6). 调用setLayoutSubviews.

drawRect会被调用的情况:

1) 调用sizeToFit后被调用(ps: 调用sizeToFit计算出size,然后系统自动调用drawRect方法);[提倡]

2) 通过设置contentMode属性值为UIViewContentModeRedraw,则在每次设置或更改frame的时候自动调用drawRect:。[不提倡]

3) 调用setNeedsDisplay/setNeedsDisplayInRect: 会触发drawRect: ,前提是rect不为0;[不提倡] 

drawRect是在loadView,viewDidLoad两方法后调用。该方法无法手动调用,只有通过调用其他方法触发.

ps : 初始化时未设置rect,则不自动调用drawRect。

小结

1) setNeedslayout: 标记为需要重新布局,但不立即调用 layoutSubview
2) layoutSubviews : 默认该方法没做什么事情。 初始化不会触发,addSubview,设置view的frame,滚动UIScrollView会触发,旋转screen,改变UIView大小会触发父view的layoutSubviews事件。
3) setNeedsDisplay,若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来调用setNeedsDisplay实时刷新屏幕
4) drawRect: 重绘,初始化设置rect大小,调用sizeThatFit后,设置contentMode属性值为UIViewContentModeRedraw,调用setNeedsDisplay则会触发该事件。
5) layoutIfNeeded: 如果有需要刷新的标记,立即调用layoutSubviews进行布局
6) sizeThatFits
7) sizeToFit: 该方法会自动调用sizeThatFits, 该方法不应在子类中被重写,应重写sizeThatFits

总结

1. 通过重写drawRect完成UIView中的绘图操作,在drawRect中获取绘图上下文(CGContextRef)。绘图后不建议通过调用drawRect方法进行绘制,而是通过调用setNeedsDisplay或者setNeedsDisplayInRect。

2. drawRect是在loadView与viewDidload之后调用


参考资料:

    苹果官方文档;

    绘制像素到屏幕上

    UIView(包括子类)的几个初始化时执行动作的时机

    UIview需要知道的一些事情:setNeedsDisplay、setNeedsLayout

 谈谈UIView的几个layout方法-layoutSubviews、layoutIfNeeded、setNeedsLayout...

     iOS 离屏渲染的研究

     iOS重绘机制drawRect

update on 5th,Nov,2016

0 0