UIView之drawRect: & layoutSubviews的作用和機制

重繪機制

iOS的繪圖操作是在UIView的drawRect中完成的,我們想要在UIView中完成繪圖(或者自定義控件)含懊,需要在UIView的拓展類(或者子類)中重寫drawRect函數(shù)身冬,在這里進行繪圖的操作,系統(tǒng)會自動調(diào)用該函數(shù)進行繪圖岔乔。
重繪也是在drawRect:中完成的酥筝,但是Apple并不建議我們直接調(diào)用drawRect:方法,如果直接調(diào)用沒有效果雏门,Apple建議我們調(diào)用setNeedDiplay方法嘿歌,調(diào)用該方法后剿配,系統(tǒng)會自動調(diào)用drawRect:方法。
我們重寫drawRect:方法可以畫自定義的圖案阅束,或者我們需要自定義View控件時也需要重寫該方法呼胚,通常該函數(shù)只會調(diào)用一次,當需要手動觸發(fā)是息裸,只需要調(diào)用setNeedDiplay方法即可蝇更。

不知道大家是否有想過下面的問題:為什么蘋果會提供drawRect機制,為什么不建議直接調(diào)用drawRect函數(shù)呼盆,而是建議我們調(diào)用setNeedDisplay ?
這里允許我通俗的描述下:我們可以認為年扩,在在創(chuàng)建視圖時,設(shè)置frame等參數(shù)后访圃,可以理解成只有一個點厨幻,然后晚些系統(tǒng)查看所有需要繪制的東西,并按順序排列,因為有些內(nèi)容是重疊的况脆,最后高效的將視圖繪制出來饭宾。這樣系統(tǒng)根據(jù)層的情況優(yōu)化性能。
另外:再說一下setNeedDisplay函數(shù)格了,加入有A看铆、B兩個VC,如果我們在當前顯示的VC A中調(diào)用[B.view drawRect]函數(shù)盛末,這時B回去繪制頁面弹惦,但是B并未顯示在window上,這就造成了一種資源的浪費悄但。所以Apple建議我們調(diào)用setNeedDisplay棠隐,這樣當B展示在Window上時再去繪制渲染視圖,充分減少資源浪費算墨。

視圖繪制相關(guān)方法

①宵荒、- (void)drawRect:(CGRect)rect;
重寫此方法,執(zhí)行重繪任務
②净嘀、- (void)setNeedsDisplay;
將視圖標記為需要重繪报咳,異步調(diào)用drawRect
③、- (void)setNeedsDisplayInRect:(CGRect)rect;
將視圖標記為需要局部重繪

drawRect調(diào)用機制

1挖藏、調(diào)用時機:loadView ->ViewDidload ->drawRect:
2暑刃、如果在UIView初始化時沒有設(shè)置rect大小,將直接導致drawRect:不被自動調(diào)用膜眠。
3岩臣、通過設(shè)置contentMode屬性值為UIViewContentModeRedraw。那么將在每次設(shè)置或更改frame的時候自動調(diào)用drawRect:宵膨。
4架谎、直接調(diào)用setNeedsDisplay,或者setNeedsDisplayInRect:觸發(fā)drawRect:辟躏,但是有個前提條件是:view當前的rect不能為nil

5谷扣、該方法在調(diào)用sizeThatFits后被調(diào)用,所以可以先調(diào)用sizeToFit計算出size捎琐。然后系統(tǒng)自動調(diào)用drawRect:方法会涎。
這里簡單說一下sizeToFit和sizeThatFit:
sizeToFit:會計算出最優(yōu)的 size 而且會改變自己的size
sizeThatFits:會計算出最優(yōu)的 size 但是不會改變 自己的 size

注意事項:

1、若使用UIView繪圖瑞凑,只能在drawRect:方法中獲取相應的contextRef并繪圖末秃。如果在其他方法中獲取到一個invalidate的ref保存下來,在drawRect中并不能用于畫圖籽御。等到在這里調(diào)用時练慕,可能當前上下文環(huán)境已經(jīng)變化惰匙。
2、若使用CALayer繪圖贺待,只能在drawInContext: 中(類似于drawRect)繪制徽曲,或者在delegate中的相應方法繪制。同樣也是調(diào)用setNeedDisplay等間接調(diào)用以上方法麸塞。
3秃臣、若要實時畫圖,不能使用gestureRecognizer哪工,只能使用touchbegan等方法來掉用setNeedsDisplay實時刷新屏幕奥此。
4、UIImageView繼承自UIView,但是UIImageView能不重寫drawRect方法用于實現(xiàn)自定義繪圖雁比。具體原因如下:
Apple在文檔中指出:UIImageView是專門為顯示圖片做的控件稚虎,用了最優(yōu)顯示技術(shù),是不讓調(diào)用darwrect方法偎捎, 要調(diào)用這個方法蠢终,只能從uiview里重寫。

layoutSubviews

這個方法是用來對subviews重新布局茴她,默認沒有做任何事情寻拂,需要子類進行重寫。
當我們在某個類的內(nèi)部調(diào)整子視圖位置時丈牢,需要調(diào)用祭钉。
反過來的意思就是說:如果你想要在外部設(shè)置subviews的位置,就不要重寫己沛。

視圖布局相關(guān)方法:

①慌核、- (void)layoutSubviews;
對subview重新布局
②、- (void)setNeedsLayout;
將視圖標記為需要重新布局申尼, 這個方法會在系統(tǒng)runloop的下一個周期自動調(diào)用layoutSubviews垮卓。
③、- (void)layoutIfNeeded;
如果有需要刷新的標記师幕,立即調(diào)用layoutSubviews進行布局(如果沒有標記粟按,不會調(diào)用layoutSubviews)這里注意一個點:標記,沒有標記们衙,即使我們掉了該函數(shù)也不起作用钾怔。
如果要立即刷新碱呼,要先調(diào)用[view setNeedsLayout]蒙挑,把標記設(shè)為需要布局,然后馬上調(diào)用[view layoutIfNeeded]愚臀,實現(xiàn)布局.
在視圖第一次顯示之前忆蚀,標記總是“需要刷新”的,可以直接調(diào)用[view layoutIfNeeded]

這里有必要描述下三者之間的關(guān)系:
在沒有外界干預的情況下,一個view的frame或者bounds發(fā)生變化時馋袜,系統(tǒng)會先去標記flag這個view,等下一次渲染時機到來時(也就是runloop的下一次循環(huán))男旗,會去按照最新的布局去重新布局視圖。
setNeedLayout就是給這個view添加一個標記欣鳖,告訴系統(tǒng)下一次渲染時機需要重新布局這個視圖察皇。
layoutIfNeed就是告訴系統(tǒng),如果已經(jīng)設(shè)置了flag泽台,那不用等待下個渲染時機到來什荣,立即重新渲染。前提是設(shè)置了flag怀酷。
layoutSubviews則是由系統(tǒng)去調(diào)用稻爬,不需要我們主動調(diào)用,我們只需要調(diào)用layoutIfNeed蜕依,告訴系統(tǒng)是否立即執(zhí)行重新布局的操作桅锄。

layoutSubviews調(diào)用時機

結(jié)論是經(jīng)過搜索得到的,基于此筆者進行了驗證样眠,并得到了些結(jié)果:
1友瘤、init初始化不會觸發(fā)layoutSubviews。
2吹缔、addSubview會觸發(fā)layoutSubviews商佑。(當然這里frame為0,是不會調(diào)用的厢塘,同上面的drawrect:一樣)
3茶没、設(shè)置view的Frame會觸發(fā)layoutSubviews,(當然前提是frame的值設(shè)置前后發(fā)生了變化晚碾。)
4抓半、滾動一個UIScrollView會觸發(fā)layoutSubviews。
5格嘁、旋轉(zhuǎn)屏幕會觸發(fā)父UIView上的layoutSubviews事件笛求。(這個我們開發(fā)中會經(jīng)常遇到,比如屏幕旋轉(zhuǎn)時糕簿,為了界面美觀我們需要修改子view的frame探入,那就會在layoutSubview中做相應的操作)
6、改變一個UIView大小的時候也會觸發(fā)父UIView上的layoutSubviews事件。
7跟衅、直接調(diào)用setLayoutSubviews闷畸。(Apple是不建議這么做的)

這里需要補充一點:layoutSubview是布局相關(guān),而drawRect則是負責繪制植旧。因此從調(diào)用時序上來講辱揭,layoutSubviews要早于drawRect:函數(shù)。

關(guān)于LayoutSubView我們再來看一個例子:
1病附、另同時用上一套的場景舉個例问窃,當想知道tableView reloadData后的contentSize的話可以在reloadData后用這兩個方法,然后就可以直接提取contentSize了完沪。
2域庇、demo完善中,稍后奉上

渲染的時機

了解了drawRect:和layoutSubviews:的原理后覆积,我們是否會想跟進一步的去了解:我在使用setNeedDisplay和setNeedLayout分別標記了需要重繪和需要重新布局后较剃,那到底什么時間去執(zhí)行的渲染操作呢?我們接下里詳細拆分講解

iOS顯示系統(tǒng):

1技健、如何讓App渲染的代碼定時執(zhí)行(例如:每秒執(zhí)行60次)写穴?
iOS 的顯示系統(tǒng)是由 VSync 信號驅(qū)動的,VSync 信號由硬件時鐘生成雌贱,每秒鐘發(fā)出 60 次(這個值取決設(shè)備硬件啊送,比如 iPhone 真機上通常是 59.97)。iOS 圖形服務接收到 VSync 信號后欣孤,會通過 IPC 通知到 App 內(nèi)馋没。App 的 Runloop 在啟動后會注冊基于端口的源也就是source1,Vsync信號則通過 mach_port 端口傳遞過來降传,同時喚醒runloop篷朵,隨后 Source1 的回調(diào)會驅(qū)動整個 App 的動畫與顯示。
tips:圖形服務同APP Process是兩個進程婆排,他們之間通信的方式是IPC,了解WKWebview實現(xiàn)機制的同學會發(fā)現(xiàn)声旺,WebContent process 同App process進行通信的方式也是通過IPC來實現(xiàn)的。有興趣的同學可以參考我的另一篇博客:關(guān)于wkwebview講解段只。
2腮猖、通過mach_port端口發(fā)送消息,喚醒Runloop后赞枕,做了一些修改view和layer的工作澈缺,并提交到全局容器,等待渲染時機到來炕婶。
Core Animation 在 RunLoop 中注冊了一個 Observer姐赡,監(jiān)聽了 BeforeWaiting 和 Exit 事件。當一個觸摸事件到來時(也可以理解成Vsync信號喚起)柠掂,RunLoop 被喚醒项滑,App 中的代碼會執(zhí)行一些操作,比如創(chuàng)建和調(diào)整視圖層級陪踩、設(shè)置 UIView 的 frame杖们、修改 CALayer 的透明度、為視圖添加一個動畫肩狂;這些操作最終都會被 CALayer 標記摘完,并通過 CATransaction 提交到一個中間狀態(tài)去。當上面所有操作結(jié)束后傻谁,RunLoop 即將進入休眠(或者退出)時孝治,關(guān)注該事件的 Observer 都會得到通知。這時 Core Animation 注冊的那個 Observer 就會在回調(diào)中审磁,把所有的中間狀態(tài)合并提交到 GPU 去顯示谈飒;
如果此處有動畫,通過 DisplayLink 穩(wěn)定的刷新機制會不斷的喚醒runloop态蒂,使得不斷的有機會觸發(fā)observer回調(diào)杭措,從而根據(jù)時間來不斷更新這個動畫的屬性值并 繪制出來。
注:動畫由CADisplayLink來不斷喚醒runloop钾恢。
3手素、具體邏輯圖:(來源于網(wǎng)絡)

image

渲染時機

1、Core Animation 在 RunLoop 中注冊了一個 Observer 監(jiān)聽 BeforeWaiting(即將進入休眠) 和 Exit (即將退出Loop) 事件 瘩蚪。
2泉懦、當在操作 UI 時,比如改變了 Frame疹瘦、更新了 UIView/CALayer 的層次時崩哩,或者手動調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,這個 UIView/CALayer 就被標記為待處理言沐,并被提交到一個全局的容器去邓嘹。當Oberver監(jiān)聽的事件到來時,回調(diào)執(zhí)行函數(shù)中會遍歷所有待處理的UIView/CAlayer 以執(zhí)行實際的繪制和調(diào)整险胰,并更新 UI 界面吴超。
3、回調(diào)函數(shù)內(nèi)部調(diào)用棧大致如下:

_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
   QuartzCore:CA::Transaction::observer_callback:
       CA::Transaction::commit();
           CA::Context::commit_transaction();
               CA::Layer::layout_and_display_if_needed();
                   CA::Layer::layout_if_needed();
                         [CALayer layoutSublayers];
                         [UIView layoutSubviews];
                   CA::Layer::display_if_needed();
                         [CALayer display];
                         [UIView drawRect];

簡單解釋下:

1鸯乃、首先是通過CATransaction提交到全局的容器中
2鲸阻、檢查是否有標記為需要重新繪制和布局的Layer
3、如果有則執(zhí)行l(wèi)ayout和redraw操作缨睡。
另外從這上面我們也可以看到:一定是先有布局鸟悴,再去繪制圖形。即:layout調(diào)用一定是在drawRect:之前奖年。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末细诸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子陋守,更是在濱河造成了極大的恐慌震贵,老刑警劉巖利赋,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異猩系,居然都是意外死亡媚送,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門寇甸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塘偎,“玉大人,你說我怎么就攤上這事拿霉∫髦龋” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵绽淘,是天一觀的道長涵防。 經(jīng)常有香客問我,道長沪铭,這世上最難降的妖魔是什么武学? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮伦意,結(jié)果婚禮上火窒,老公的妹妹穿的比我還像新娘。我一直安慰自己驮肉,他們只是感情好熏矿,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著离钝,像睡著了一般票编。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卵渴,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天慧域,我揣著相機與錄音,去河邊找鬼浪读。 笑死昔榴,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的碘橘。 我是一名探鬼主播互订,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痘拆!你這毒婦竟也來了仰禽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吐葵,沒想到半個月后规揪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡温峭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年猛铅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诚镰。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祥款,靈堂內(nèi)的尸體忽然破棺而出清笨,到底是詐尸還是另有隱情,我是刑警寧澤刃跛,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布抠艾,位于F島的核電站,受9級特大地震影響桨昙,放射性物質(zhì)發(fā)生泄漏检号。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一蛙酪、第九天 我趴在偏房一處隱蔽的房頂上張望齐苛。 院中可真熱鬧,春花似錦桂塞、人聲如沸凹蜂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玛痊。三九已至,卻和暖如春狂打,著一層夾襖步出監(jiān)牢的瞬間擂煞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工趴乡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留对省,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓晾捏,卻偏偏與公主長得像官辽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子粟瞬,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容