解釋
更新布局總會重新觸發layoutSubviews
方法。
-
layoutSubviews
繼承于UIView
的子類重寫,進行布局更新,刷新視圖。如果某個視圖自身的bounds
或者子視圖的bounds
發生改變,那么這個方法會在當前runloop
結束的時候被調用。為什么不是立即調用呢?因為渲染畢竟比較消耗性能,特別是視圖層級復雜的時候。這種機制下任何UI控件布局上的變動不會立即生效,而是每次間隔一個周期,所有UI控件在布局上的變動統一生效并且在視圖上更新,蘋果通過這種高性能的機制保障了視圖渲染的流暢性。
layoutSubviews方法調用棧.png
從上圖中可以看到,runloop
的observer
回調=>CoreAnimation
渲染引擎一次事務的提交=>CoreAnimation
遞歸查詢圖層是否有布局上的更新=>CALayer
layoutSublayers
=>UIView
layoutSubviews
這樣一個調用的流程。從這里也可以看到UIView
其實就是相當于CALayer
的代理。
drawRect方法調用棧
順便看一眼drawRect
方法的調用棧,從CA::Layer::layout_and_display_if_needed
方法之前都是一樣的。
-
setNeedsLayout
標記為需要重新布局,異步調用layoutIfNeeded
刷新布局,不立即刷新,在下一輪runloop結束前刷新,對于這一輪runloop
之內的所有布局和UI上的更新只會刷新一次,layoutSubviews
一定會被調用。 -
layoutIfNeeded
如果有需要刷新的標記,立即調用layoutSubviews
進行布局(如果沒有標記,不會調用layoutSubviews
)。
關鍵點
-
layoutIfNeeded
不一定會調用layoutSubviews方法。 -
setNeedsLayout
一定會調用layoutSubviews
方法(有延遲,在下一輪runloop結束前)。 - 如果想在當前
runloop
中立即刷新,調用順序應該是
[self setNeedsLayout];
[self layoutIfNeeded];
反之可能會出現布局錯誤的問題。