从 setNeedsLayout 说起
本文从 setNeedsLayout 这个方法说起,分享与其相关的 UIKit 视图交互、使用场景等内容。
UIKit 为 UIView 提供了这些方法来进行视图的更新与重绘:
public func setNeedsLayout()
public func layoutSubviews()
public func layoutIfNeeded()
public func setNeedsDisplay()
public func setNeedsDisplayInRect(rect: CGRect)
public func drawRect(rect: CGRect)
运行时视图交互模型
无论是用户交互触发还是代码自动触发,下图展示的事件序列都同样适用,这里用到了 setNeedsLayout 方法:
UIKit interactions with your view objects
上图对应的事件序列如下:
用户触摸屏幕
硬件报告触摸事件给 UIKit 框架
UIKit 框架将触摸事件打包成 UIEvent 对象,然后分发给合适的视图
事件处理代码会对相应事件作出响应,代码可以是这样的:
-更改 frame、bounds、alpha 等属性
-调用 setNeedsLayout 方法以标记该视图(或者它的子视图)为需要进行布局更新
-调用 setNeedsDisplay 或者 setNeedsDisplayInRect: 方法以标记该视图(或者它的子视图)需要进行重画
-通知 Controller 有数据变化
如果一个视图的几何结构改变了,UIKit 会更新它的子视图
如果任何视图的任何部分被标记为需要重画,UIKit 会要求视图重画自身
任何已经更新的视图会与应用余下的可视内容组合在一起,同时被发送到图形硬件去显示
图形硬件将已解释内容转化到屏幕上
方法调用逻辑
在上面的过程中,我们可以接触到文章开头提到的方法,他们的调用逻辑是这样的:
setNeedsLayout 会给当前 UIView 立一个 flag,以表示后续应该调用 layoutSubviews 方法,以调整当前视图及其子视图的布局。
setNeedsDisplayInRect: 会给当前 UIView 立一个 flag,以表示后续应该调用 drawRect: 方法,以进行视图重绘。
View Drawing Cycle
Apple 官方文档已经明确说明,开发者不应该直接调用 layoutSubviews 与 drawRect: ,而应该在你认为系统默认的布局和重绘不能带给你想要的效果时,在子类中重写这些方法,然后分别通过 setNeedsLayout 和 setNeedsDisplayInRect: 来进行调用。
当然你可以给多个 UIView 设置 setNeedsLayout,然后当下一个 View Drawing Cycle 到来时,多个 UIView 的视图会一同更改布局。
那么这个 View Drawing Cycle 到底是什么呢,官方是这样解释的:
The system waits until the end of the current run loop before initiating any drawing operations. This delay gives you a chance to invalidate multiple views, add or remove views from your hierarchy, hide views, resize views, and reposition views all at once. All of the changes you make are then reflected at the same time.
显然这样用 RunLoop 把多次修改聚集在一个 Cycle 一并进行渲染是更加高效的行为。
(我个人对 View Drawing Cycle 的理解是这样的:UIKit 需要处理非常多的事件,这些事件组合起来变成了一个非常复杂的事件序列,在这个序列中有些特定的点是 UIKit 专门提供给 UIView 来进行视图更改的。如上所述,在当前 run loop 结束之前,我们有机会做各种视图更改,并且这些更改会在下一个 run loop 体现出来,所以 View Drawing Cycle 就是一次次 run loop 中我们通过 UIKit 得到的 UIView 重布局、重绘机会所组成的循环。有理解不对的地方,欢迎评论指正。)
如何善用 View Drawing Cycle
一个很常见的例子是,一个 iPad App,横屏和竖屏时界面布局不一样,那么你可以监听设备旋转,在设备旋转时执行 setNeedsLayout 方法,然后在 layoutSubviews 里面通过判断接下来是横屏还是竖屏来进行不一样的布局设置。基本上你不可能只在这个方法里只进行了单个 UIView 的布局修改,而是多项修改,那么 App 会在下一个 View Drawing Cycle 到来时,把这些修改一起执行,这是最正常的情况。
那么假如我不按 Apple 规定的来,直接调用 layoutSubviews 呢?我们可以猜想一下:因为这个方法里面提供了我们需要的布局方式,所以 UIView 会按我们想要的方式来布局,但是因为各种视图修改的请求时机是零碎的,所以这样效率会低一些。所以重要的其实是了解何时会触发 layoutSubviews:
init 初始化不会触发 layoutSubviews
addSubview 会触发 layoutSubviews
设置 view 的 frame 会触发 layoutSubviews,当然前提是 frame 的值设置前后发生了变化
滚动一个 UIScrollView 会触发 layoutSubviews
旋转 Screen 会触发父 UIView 上的 layoutSubviews 事件
改变一个 UIView 大小的时候也会触发父 UIView 上的 layoutSubviews 事件
然后按 Apple 要求的方式来做就好了(分别通过 setNeedsLayout 和 setNeedsDisplayInRect: 来调用 layoutSubviews 和 drawRect:)
但有些情况比较特殊:你打开 iOS 的时钟应用,去看里面的秒表页面,这个页面里面的两个按钮是没有 UIButton 默认的动画的,点击之后,按钮会瞬间改变自身的状态(颜色、内部 Label 的内容),这种情况我们需要跳出 View Drawing Cycle,来实现一个瞬间改变的效果。实现方法如下:
extension UIButton {
func quickButtonAction() {
UIView.performWithoutAnimation({
// do something
self.layoutIfNeeded()
})
}
}
可以看出 layoutIfNeeded 作为一个辅助选项给了 setNeedsLayout 一个可以瞬时执行的特点。当然默认这个“选项”是关闭的。
setNeedsDisplay 补充
setNeedsLayout 的使用场景之前已经提过了(iPad App),下面举个栗子说一下 setNeedsDisplayInRect:的使用场景。
假如我需要在两点之间绘制一条直线,有两个 dotView,需要绘制一个 lineView。我在 drawRect: 方法里实现了 lineView 的具体绘制方法(根据两个点来绘制)。那么如果我想要这个直线一直根据两个点同步变化的话,就需要在 dotView 的位置发生改变时,执行:
lineView.setNeedsDisplay() // 重绘 lineView
至于 drawRect: 方法什么时候会被触发:
From StackOverFlow
一个很好的参考链接:What is the relationship between UIView’s setNeedsLayout, layoutIfNeeded and layoutSubviews?
从 setNeedsLayout 说起的更多相关文章
- UIView的setNeedsDisplay和setNeedsLayout
1,UIView的setNeedsDisplay和setNeedsLayout方法 首先两个方法都是异步执行的.而setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到 UI ...
- setNeedsDisplay,setNeedsLayout
UIView的setNeedsDisplay和setNeedsLayout方法.首先两个方法都是异步执行的.setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到UIGraph ...
- setNeedsDisplay和setNeedsLayout
1,UIView的setNeedsDisplay和setNeedsLayout方法 首先两个方法都是异步执行的.而setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到 UI ...
- UIView常用的一些方法小记之setNeedsDisplay和setNeedsLayout
1,UIView的setNeedsDisplay和setNeedsLayout方法 首先两个方法都是异步执行的.而setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到 UI ...
- layoutSubviews -- setNeedsLayout -- layoutIfNeeded -- 区别
-layoutSubviews方法:这个方法,默认没有做任何事情,需要子类进行重写-setNeedsLayout方法: 标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,但 ...
- iOS之layout方法-layoutSubviews、layoutIfNeeded、setNeedsLayout
下面列举下iOS layout的相关方法: layoutSubviews layoutIfNeeded setNeedsLayout setNeedsDisplay drawRect sizeThat ...
- 【转】 UIview需要知道的一些事情:setNeedsDisplay、setNeedsLayout
原文:http://blog.sina.com.cn/s/blog_923fdd9b0101b2b4.html 1.在Mac OS中NSWindow的父类是NSResponder,而在iOS 中UIW ...
- setNeedsDisplay setNeedsLayout
setNeedsDisplay调用drawRect方法来实现view的绘制,而setNeedsLayout则调用layoutSubView来实现view中subView的重新布局 转自 http:/ ...
- UIView的setNeedsLayout, layoutIfNeeded 和 layoutSubviews 方法之间的关系解释
转自:http://blog.csdn.net/meegomeego/article/details/39890385 layoutSubviews总结 ios layout机制相关方法 - (CGS ...
随机推荐
- 11个强大的Visual Studio调试小技巧(转)
简介 调试是软件开发周期中很重要的一部分.它具有挑战性,同时也很让人疑惑和烦恼.总的来说,对于稍大一点的程序,调试是不可避免的.最近几年,调试工具的发展让很多调试任务变的越来越简单和省时. 这篇文章总 ...
- maven 的 oracle的Missing artifact com.oracle:******:jar:11.2.0.2.0
解决方法: 下载ojdbc6对应版本号的包.把下载的包放到: 然后dos命令: -Dpackaging=jar -Dfile=ojdbc6.jar 主:必须确保已经配置了你maven,如何配置mave ...
- Linux内核中流量控制
linux内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下:而应用层上的控制是通过iproute2软件包中的tc来实现, tc和sched的关系就好象iptables和netfi ...
- hdu4705Y
链接 这题可以算树形DP吧 树上的递推 对于树上的某个节点 反着算比较好做 就是算有多少有simple路径的 固定某个节点u 另两个节点 有两种取法 1.从不同子树里各选一个 2.从所有子树里选一个 ...
- ArcGIS for Android示例解析之高亮要素-----HighlightFeatures
转自:http://blog.csdn.net/wozaifeiyang0/article/details/7323606 HighlightFeatures 要素高亮化功能,相信有其他gis开发经营 ...
- poj2686 Traveling by Stagecoach
http://poj.org/problem?id=2686 Trav ...
- 利用 jQuery-photoClip插件 实现移动端裁剪功能并以Blob对象上传
最近客户要求实现论坛贴子附件裁剪功能,没有考虑js与ios.android容器交互解决方案,单纯用js去实现它的.由于本来附件上传用的别的插件实现的,所以是在此基础上费了不少劲,才把jQuery-ph ...
- winpcap使用之捕获数据包
第一种方法,调用回调函数 #include "pcap.h" /* packet handler 函数原型 */ void packet_handler(u_char *param ...
- 【UER #1】跳蚤OS(Trie)
跳蚤OS 是跳蚤国自主研发的功能强大的操作系统. 跳蚤OS的文件系统与普通的文件系统类似,是个文件夹套文件夹的结构.文件系统根目录称为“//”.我们可以用文件路径来表明文件所在的位置,比如“/flea ...
- centos "cannot open display"的问题
实用技巧:在Linux下设置xhost方法步骤 第一步:用root登陆linux,启动vnc服务: 第二步:根据vnc起来的端口,设置export DISPLAY=localhost:1(1表示vnc ...