UIView:这些函数你清楚吗

开头说几句

UIView表示屏幕上的一块矩形区域,App上看得见,摸得着的都是UIView或者其子类对象。其主要功能有:

  • Drawing and animation(绘图与动画)
  • Layout and subview management(布局与子视图的管理)
  • Event handling(事件处理)

这篇文章主要会讲一下UIView的一些细节方面,对基础知识不做探究了。

老生常谈,先来看看这些常见的函数。如果看过类似的,不要走,跳过这一节。

关于绘图与布局

setNeedsDisplay

将视图标记为需要重绘,并异步调用drawRect:

setNeedsDisplayInRect:(CGRect)rect

将视图标记为需要局部重绘

drawRect:(CGRect)rect

重写此方法,执行重绘任务

drawRect被调用情况:

  • ViewController -> loadView -> viewDidLoad之后(但当UIView在初始化时未设置rect大小,将导致drawRect不被自动调用)
  • 调用了sizeToFit之后
  • 设置了view.contentMode = UIViewContentModeRedraw,每次设置或更改frame之后(不推荐使用)
  • 调用了setNeedsDisplayORsetNeedsDisplayInRect:之后,(但rect ≠ 0)
Tips:
- 不可显式调用,只能通过setNeedsDisplay或者setNeedsDisplayInRect系统自动调用
- 使用UIView绘图,只能在方法内;使用CALayer绘图,只能在drawInContext:中,或在delegate的相应方法

drawRect可以提供给我们在自定义界面时的方便,但也要慎用。
具体可以看看这篇链接: 内存恶鬼drawRect


layoutSubviews

布局子视图。默认没有执行什么过程,需要子类重写。

layoutSubviews被调用情况:

  • 调用了initWithFrame:,且rect ≠ CGRectZero时
  • 调用了addSubview
  • 设置了frame,且oldFrame ≠ newFrame
  • Screen发生变化,比如UIScrollView滚动,屏幕旋转,来电等
Tips:若想在外部设置subviews位置时,则不要重写它

setNeedsLayout

标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新。且layoutSubviews一定会被调用

layoutIfNeed

如果有需要刷新的标记,立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)


sizeThatFits:

让视图计算最适合子视图的大小,即能把全部子视图显示出来所需要的最小的size

 Tips:sizeThatFits传入的参数是接收者当前的size,返回一个适合的size

sizeToFit

根据子视图的大小位置,调整视图,使其恰好围绕子视图。也就是说自动适应子视图的大小,只显示子视图

这个方法经常用于UILabel一类控件,使用sizeToFit能使得控件根据文字来调整尺寸。(或者不方便手动调整尺寸的地方,譬如很多系统的原生控件我们无法改变大小,估计就是据此原理)

 Tips:
 - 不应该在子类被重写,只能重写sizeThatFits:
 - sizeToFit会调用sizeThatFits:
 - sizeToFit跟sizeThatFits都无传递链,只对自身负责

小结

类似的还有约束处理的这几个函数

- (BOOL)needsUpdateConstraints //视图的约束是否需要更新
- (void)setNeedsUpdateConstraints //设置视图的约束需要更新
- (void)updateConstraints //为视图更新约束
- (void)updateConstraintsIfNeeded //更新视图和其子视图的约束

细致地了解这些关键函数,根据iOS的显示刷新原理我们能更好简便地去使用它们。譬如界面约束更改的动态展示,以及view线条的动态绘制等等。

关于展示与动画

CALayer

为什么要扯到CALayer呢?其实UIView之所以有显示能力,主要是来源于自身具备着的CALayer。CALayer基于图像管理内容并允许你在这些内容上创建动画。一言以蔽之,UIView来自CALayer,高于CALayer,是CALayer高层实现与封装;UIView的很多特性都源于CALayer对它的支持。

把UIView看成一张相片,那么CALayer就是相框。相框有圆形等各种形状,有边框粗细,有边框颜色等等,所以依靠定制CALayer,则能实现各式各样的UIView。UIView有各式各样的动态效果,也是依赖于CALayer的变换。CALayer的我想再起一文来讲,所以这里就暂且一笔带过了。

关于事件

iOS系统在处理事件时,通过UIApplication对象和每个UIWindow对象的sendEvent:方法将事件分发给具体处理此事件的responder对象(对于触摸事件为hit-test view,其他事件为first responder),当具体处理此事件的responder不处理此事件时,可以通过responder chain交给上一级处理。

事件的传递链

如下图所示,iOS中事件传递首先从UIApplication开始,接着传递到UIWindow,再到UIView、Subview。传递顺序从下到上。

PS: 如果是VC注册了手势操作,在UIWindow接着往下传递到View之前,Window会将事件交给GestureRecognizer,如果在此期间,GestureRecognizer识别了传递过来的事件,则该事件将不会继续传递到下一级去,而是交给Target(ViewController)进行处理。以此类推。

iOS事件传递

事件的响应链

事件的响应顺序跟传递顺序是相反的。响应顺序是从上到下。

接下来请带着一个问题来看下面的知识点:当你点击了一下屏幕时那一瞬间整个应用发生了什么事情?

touchesBegan../Moved../Ended..

应用找到合适的视图时,就会调用视图控件的touches系列方法进行处理。

Tips:在某个视图实现了此方法后,会拦截事件的响应。父视图一类无法监听到触摸事件。

pointInside:withEvent

判断是否点在视图上.

Tips: 如果我们不想让某个视图响应事件,也可以重载pointInside:withEvent:方法,让此方法返回NO

hitTest: withEvent:

hit-Testing的作用就是找出这个触摸点下面的View是什么,HitTest会检测这个点击的点是不是发生在这个View上,如果是的话,就会去遍历这个View的subviews,直到找到最小的能够处理事件的view,如果整了一圈没找到能够处理的view,则返回自身。

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
//recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system

Tips:
- hitTest时会调用pointInside:withEvent:
- 若出现以下情况时,hitTest会直接返回nil而不会调用pointInside:
    - view.userInteractionEnabled = NO;
    - view.enabled = NO(UIControl);
    - view.alpha <= 0.01;
    - view.hidden = YES;
- 默认的hit-testing顺序是按照UIView中Subviews的逆顺序
- 如果同级别的Subview中有重叠的部分,则优先检查顶部的Subview,如果顶部的Subview返回nil,再检查底部的Subview
- 若点击事件没有发生在视图A上,是会忽略视图A的subview,即使subview.clipsToBounds = NO且超出A的边界。


每当我们点击了一下iOS设备的屏幕,UIKit就会生成一个事件对象UIEvent,然后会把这个Event分发给当前active的app。

官方原文 Then it places the event object in the active app’s event queue.

告知当前活动的app有事件之后,UIApplication 单例就会从事件队列中去取最新的事件,然后分发给能够处理该事件的对象。UIApplication 获取到Event之后,Application就纠结于到底要把这个事件传递给谁,这时候就要依靠HitTest来决定了, histTest返回结果不为nil的时候则默认不再向父视图传递。

小结

讲了这些有什么用呢?很多时候不规则按钮一样的定制,比如一个圆形按钮,屏蔽圆形按钮四个角的触摸事件怎么去处理?在滚动视图的优化上,类似微博,twitter这种每个cell可能承载大量子视图,而且又会有很多数据时,大量的UIView是会占用性能的,使用CALayer+hitTest就可以省略了很多性能消耗。

最后讲两句

这里只是讲些浅显的东西,只不过我们在用每个函数时,准确理解其意义跟原理有助于我们让代码更简洁有效。
关于iOS的界面性能优化上,本来也想讲讲,不过大牛ibireme这篇文章 iOS 保持界面流畅的技巧 已经写得很全面详细了,个人简直拜服,这里就直接推荐出来了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 7、不使用IB是,下面这样做有什么问题? 6、请说说Layer和View的关系,以及你是如何使用它们的。 1.首先...
    AlanGe阅读 650评论 0 1
  • 一、初始化方法 1、- initWithFrame: UIView *view = [[UIView alloc]...
    默默_David阅读 2,476评论 1 3
  • Core Animation基础 Core Animation 利用了硬件加速和架构上的优化来实现快速渲染和实时动...
    独木舟的木阅读 1,523评论 0 3
  • UIView与UIWindow Uiview需要一个窗口UIWindow来展示页面,而UIWindow类似于一个U...
    Tuberose阅读 3,880评论 1 20
  • 登长城是需要勇气的,特别是这座被誉为“天下第一关”的居庸关,因山势陡峭,对于习惯了宅家的年轻人来说能攀登到顶...
    枧文阅读 253评论 1 1