layoutIfNeeded, setNeedsLayout
setNeedsUpdateConstraints, updateConstraintsIfNeeded
setNeedsDisplay
一. setNeedsUpdateConstraints, updateConstraintsIfNeeded
這兩個方法涉及到Auto Layout相關(guān)知識 autolayout詳細說明的文章
Auto Layout布局過程涉及延遲機制,并非一有約束更新就馬上進行布局重繪,當有約束更改時,系統(tǒng)的默認做法是延遲更新,目的是實現(xiàn)批量更改約束、繪制視圖,避免頻繁遍歷視圖層級,優(yōu)化性能。當更新約束太慢影響到后序代碼邏輯,也可強制馬上更新。

- 關(guān)于Auto Layout的布局流程,Apple給出圖示如上:即Layout Cycle是一個在App運行循環(huán)RunLoop下循環(huán)執(zhí)行的一個過程。
- App啟動后開啟RunLoop,循環(huán)檢測圖層樹中是否存在約束變化;
- 當發(fā)生Constrints Change(直接or間接設(shè)置、更新、移除約束),RunLoop檢測到約束變化;
- RunLoop發(fā)現(xiàn)約束變化后,就會進入Deferred Layout階段,視圖的位置、尺寸值會在這個過程計算,設(shè)置到對應(yīng)視圖上,并繪制出來;
- 執(zhí)行完一輪布局,RunLoop會繼續(xù)檢查視圖樹的約束更新情況,當再次發(fā)現(xiàn)約束更新,則執(zhí)行新一輪布局。
Constraints Change
Changes to constraint expressions
Activating or deactivating
Setting the constant or priority
Adding or removing views
Engine recomputes the layout
Engine variables receive new values
Views call superview.setNeedsLayout()
Deferred Layout Pass
Reposition misplaced views
Two passes through the view hierarchy
Update constraints
Reassign view frames
Request via setNeedsUpdateConstraints()
Often not needed
Initial constraints in IB
Separate logic is harder to follow
Implement it when
Changing constraints in place is too slow
A view is making redundant changes
Traverse the view hierarchy, top-down
Call layoutSubviews()(or layout() on OSX)
Position the view`s subviews
Copy subview frames from the layout engine
對于重寫layoutSubviews,Apple的一些建議
Overriding layoutSubviews
Override when constraints are insufficient
Some views have already been laid out
DO
Invoke super.layoutSubviews()
Invalidate layout within your subtree
Don`t
Call setNeedsUpdateConstraints()
Invalidate layout outside your subtree
Modify constraints indiscriminately
1. updateConstraints
在上面提到的Deferred Layout Pass過程包括兩個步驟:Update Constraints和Reassign View Frames。就是在Update Constraints過程中調(diào)用的這個方法。自定義view應(yīng)該重寫此方法在其中建立constraints. 注意:要在實現(xiàn)的最后調(diào)用[super updateConstraints]。
2. setNeedsUpdateConstraints
當一個自定義view的某個屬性發(fā)生改變,并且可能影響到constraint時,需要調(diào)用此方法去標記constraints需要在未來的某個點更新,系統(tǒng)然后調(diào)用updateConstraints.
3. needsUpdateConstraints
當constraint-based layout system使用此返回值去決定是否需要調(diào)用updateConstraints作為正常布局過程的一部分。
4. updateConstraintsIfNeeded
立即觸發(fā)約束更新,自動更新布局。
二. layoutIfNeeded, setNeedsLayout
當在操作 UI 時,比如改變了 Frame、更新了 UIView/CALayer 的層次時,或者手動調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,這個 UIView/CALayer 就被標記為待處理,并被提交到一個全局的容器去。
蘋果注冊了一個 Observer 監(jiān)聽 BeforeWaiting(即將進入休眠) 和 Exit (即將退出Loop) 事件,回調(diào)去執(zhí)行一個很長的函數(shù):_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。這個函數(shù)里會遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實際的繪制和調(diào)整,并更新 UI 界面。
這個函數(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. layoutSubviews
繼承于UIView的子類重寫,進行布局更新,刷新視圖。如果某個視圖自身的bounds或者子視圖的bounds發(fā)生改變,那么這個方法會在當前runloop結(jié)束的時候被調(diào)用。為什么不是立即調(diào)用呢?因為渲染畢竟比較消耗性能,特別是視圖層級復(fù)雜的時候。這種機制下任何UI控件布局上的變動不會立即生效,而是每次間隔一個周期,所有UI控件在布局上的變動統(tǒng)一生效并且在視圖上更新,蘋果通過這種高性能的機制保障了視圖渲染的流暢性。

從上圖中可以看到,runloop的observer回調(diào)=>CoreAnimation渲染引擎一次事務(wù)的提交=>CoreAnimation遞歸查詢圖層是否有布局上的更新=>CALayer layoutSublayers=>UIView layoutSubviews 這樣一個調(diào)用的流程。從這里也可以看到UIView其實就是相當于CALayer的代理。

順便看一眼drawRect方法的調(diào)用棧,從CA::Layer::layout_and_display_if_needed方法之前都是一樣的。
layoutSubviews在以下情況下會被調(diào)用:
1、init初始化不會觸發(fā)layoutSubviews,但是是用initWithFrame 進行初始化時,當rect的值不為CGRectZero時,也會觸發(fā);
2、addSubview會觸發(fā)layoutSubviews;
3、設(shè)置view的Frame會觸發(fā)layoutSubviews,當然前提是frame的值設(shè)置前后發(fā)生了變化;
4、滾動一個UIScrollView會觸發(fā)layoutSubviews;
5、旋轉(zhuǎn)Screen會觸發(fā)父UIView上的layoutSubviews事件;
6、改變一個UIView大小的時候也會觸發(fā)父UIView上的layoutSubviews事件。
在蘋果的官方文檔中強調(diào):
You should override this method only if the autoresizing behaviors of the subviews do not offer the behavior you want.
layoutSubviews, 當我們在某個類的內(nèi)部調(diào)整子視圖位置時,需要調(diào)用。
反過來的意思就是說:如果你想要在外部設(shè)置subviews的位置,就不要重寫。
2. setNeedsLayout
setNeedsLayout用來標記為需要重新布局,異步調(diào)用layoutIfNeeded刷新布局,不立即刷新,在下一輪runloop結(jié)束前刷新,對于這一輪runloop之內(nèi)的所有布局和UI上的更新只會刷新一次,layoutSubviews一定會被調(diào)用。
3. layoutIfNeeded
如果有需要刷新的標記,調(diào)用了layoutIfNeeded會立即調(diào)用layoutSubviews進行布局(如果沒有標記,不會調(diào)用layoutSubviews)。
如果要立即刷新,要先調(diào)用[view setNeedsLayout],把標記設(shè)為需要布局,然后馬上調(diào)用[view layoutIfNeeded],實現(xiàn)布局
在視圖第一次顯示之前,標記總是“需要刷新”的,可以直接調(diào)用[view layoutIfNeeded]。
三. setNeedsDisplay
1. drawRect
drawRect在以下情況下會被調(diào)用:
1、如果在UIView初始化時沒有設(shè)置rect大小,將直接導(dǎo)致drawRect不被自動調(diào)用。drawRect調(diào)用是在Controller->loadView, Controller->viewDidLoad 兩方法之后掉用的.所以不用擔心在控制器中,這些View的drawRect就開始畫了.這樣可以在控制器中設(shè)置一些值給View(如果這些View draw的時候需要用到某些變量值).
2、該方法在調(diào)用sizeToFit后被調(diào)用,所以可以先調(diào)用sizeToFit計算出size。然后系統(tǒng)自動調(diào)用drawRect:方法。
3、通過設(shè)置contentMode屬性值為UIViewContentModeRedraw。那么將在每次設(shè)置或更改frame的時候自動調(diào)用drawRect:。
4、直接調(diào)用setNeedsDisplay,或者setNeedsDisplayInRect:觸發(fā)drawRect:,但是有個前提條件是rect不能為0。
以上1,2推薦;而3,4不提倡
drawRect方法使用注意點:
1、若使用UIView繪圖,只能在drawRect:方法中獲取相應(yīng)的contextRef并繪圖。如果在其他方法中獲取將獲取到一個invalidate的ref并且不能用于畫圖。drawRect:方法不能手動顯示調(diào)用,必須通過調(diào)用setNeedsDisplay 或者 setNeedsDisplayInRect,讓系統(tǒng)自動調(diào)該方法。
2、若使用CAlayer繪圖,只能在drawInContext: 中(類似于drawRect)繪制,或者在delegate中的相應(yīng)方法繪制。同樣也是調(diào)用setNeedDisplay等間接調(diào)用以上方法
3、若要實時畫圖,不能使用gestureRecognizer,只能使用touchbegan等方法來掉用setNeedsDisplay實時刷新屏幕
2. setNeedsDisplay、setNeedsDisplayInRect
setNeedsDisplay和setNeedsDisplayInRect
四. Auto Layout Process 自動布局過程(以上3個之間的關(guān)系)
與使用springs and struts(autoresizingMask)比較,Auto layout在view顯示之前,多引入了兩個步驟:updating constraints 和laying out views。
每一個步驟都依賴于上一個。display依賴layout,而layout依賴updating constraints。顯示之前首先得知道布局,想要完整的布局就得更新約束(約束才能得出布局?。?。
updating constraints -> layout -> display
第一步:updating constraints,被稱為測量階段,其從下向上(from subview to super view),為下一步layout準備信息。
可以通過調(diào)用方法setNeedUpdateConstraints去觸發(fā)此步。constraints的改變也會自動的觸發(fā)此步。但是,當你自定義view的時候,如果一些改變可能會影響到布局的時候,通常需要自己去通知Auto layout,updateConstraintsIfNeeded。
自定義view的話,通常可以重寫updateConstraints方法,在其中可以添加view需要的局部的contraints。
第二步:layout,其從上向下(from super view to subview),此步主要應(yīng)用上一步的信息去設(shè)置view的center和bounds??梢酝ㄟ^調(diào)用setNeedsLayout去觸發(fā)此步驟,此方法不會立即應(yīng)用layout。如果想要系統(tǒng)立即的更新layout,可以調(diào)用layoutIfNeeded。另外,自定義view可以重寫方法layoutSubViews來在layout的工程中得到更多的定制化效果。
第三步:display,此步時把 view 渲染到屏幕上,它與你是否使用Auto layout無關(guān),其操作是從上向下(from super view to subview),通過調(diào)用setNeedsDisplay觸發(fā),
因為每一步都依賴前一步,因此一個display可能會觸發(fā)layout,當有任何layout沒有被處理的時候,同理,layout可能會觸發(fā)updating constraints,當constraint system更新改變的時候。
需要注意的是,這三步不是單向的,constraint-based layout是一個迭代的過程,layout過程中,可能去改變constraints,有一次觸發(fā)updating constraints,進行一輪layout過程。
注意:如果你每一次調(diào)用自定義layoutSubviews都會導(dǎo)致另一個布局傳遞,那么你將會陷入一個無限循環(huán)中。
就是說,layout 和 updateConstraints 不斷迭代最終確立了整個布局和顯示,然后交給屏幕去顯示。
有興趣可以看看下面的文章
iOS UIView異步繪制
iOS CoreAnimation 渲染流程
iOS 圖像顯示原理、UI卡頓掉幀、異步繪制、離屏渲染