標籤:介面 開發 uiviewcontroller uiview 代碼
UIView裡暴露了以下介面,換做之前我是不會管的,基本不會用到;最近由於用到一個第三方代碼,需要對其進行改造,不瞭解清楚的話,感覺是如鯁在喉,化了半天時間做個調研。
我以前做過功能手機上的開發,那個年代沒有智能作業系統,每家公司都做自己的作業系統,其中重要且複雜的一塊就是關於螢幕上繪製:
1,想象一下,一個uiviewcontroller是蓋在另一個uiviewcontroller至少,或者一個view遮蓋了另外一個view,也許只遮蓋了部分,那麼被遮蓋部分是無需顯示到螢幕上的;再複雜一點,上層的view設定了半透明,那麼這時下面的view也需要透一點點出來;
2,省電以及效率:顯示在螢幕的東西其實都是系統通過一個個寫像素點,這是個耗電,耗時間的事;為了省電,除非有視圖變化,系統不會重新整理頁面;如果不是要求即時性特高,系統還會降低重新整理頻率;省電並提高效率的另一個途徑就是只重新整理螢幕中需要重新整理的部分。
先介紹到此,我們看看與uiview繪製相關的介面如下,分為三組,它們之間相互獨立,又相互關聯
第一組:布局/定位相關:
(void)setNeedsLayout:在receiver標上一個需要被重新布局的標記,在系統runloop的下一個周期自動調用layoutSubviews。
-
(void)layoutIfNeeded:方法如其名,UIKit會判斷該receiver是否需要layout.根據Apple官方文檔,layoutIfNeeded方法應該是這樣的layoutIfNeeded遍曆的不是superview鏈,應該是subviews鏈。
-
(void)layoutSubviews:這是核心函數,最終的目的就是調用該函數,開發人員不能調用直接調用該函數,但可以重寫該函數,來加入些自己的代碼。該函數只會進行位置,視圖大小的數字計算,並不會引起螢幕的繪製。
第二組:顯示相關:
-
(void)setNeedsDisplay:在receiver標上一個需要被重新繪圖的標記,在下一個draw周期自動重繪,iphone device的重新整理頻率是60hz,也就是1/60秒後重繪;
-
(void)setNeedsDisplayInRect:(CGRect)rect:不但設定了flag,而且詳細規定了需要重新整理的地區。
-
(void)drawRect:(CGRect)rect:這是核心函數,最終導致顯示到螢幕上。開發人員不可以直接調用該函數,只能重寫該函數,額外做一些我們想做的事。是對receiver的重繪,能獲得context。
第三組:約束相關
setNeedsUpdateConstraints:當一個自訂view的某個屬性發生改變,並且可能影響到constraint時,需要調用此方法去標記constraints需要在未來的某個點更新,系統然後調用updateConstraints.
needsUpdateConstraints:constraint-based layout
system使用此傳回值去決定是否需要調用updateConstraints作為正常版面配置階段的一部分。
updateConstraintsIfNeeded:立即觸發約束更新,自動更新布局。
updateConstraints:自訂view應該重寫此方法在其中建立constraints.注意:要在實現在最後調用[superupdateConstraints]
然後再來說說它們的關係,實際上這是一個view從計算大小,布局,到顯示到螢幕上的過程,遵循一定的順序:
版面配置階段
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準備資訊。可以通過調用方法setNeedUpdateConstraints去觸發此步。constraints的改變也會自動的觸發此步。但是,當你自訂view的時候,如果一些改變可能會影響到布局的時候,通常需要自己去通知Auto layout,updateConstraintsIfNeeded。
自訂view的話,通常可以重寫updateConstraints方法,在其中可以添加view需要的局部的contraints。
第二步:layout,其從上向下(from super view to subview),此步主要應用上一步的資訊去設定view的center和bounds。可以通過調用setNeedsLayout去觸發此步驟,此方法不會立即應用layout。如果想要系統立即的更新layout,可以調用layoutIfNeeded。另外,自訂view可以重寫方法layoutSubViews來在layout的工程中得到更多的定製化效果。
第三步:display,此步時把view渲染到螢幕上,它與你是否使用Auto layout無關,其操作是從上向下(from super view to subview),通過調用setNeedsDisplay觸發,
因為每一步都依賴前一步,因此一個display可能會觸發layout,當有任何layout沒有被處理的時候,同理,layout可能會觸發updating constraints,當constraint system更新改變的時候。
需要注意的是,這三步不是單向的,constraint-based
layout是一個迭代的過程,layout過程中,可能去改變constraints,有一次觸發updating constraints,進行一輪layout過程。
注意:如果你每一次調用自訂layoutSubviews都會導致另一個布局傳遞,那麼你將會陷入一個無限迴圈中。
不能直接調用layoutSubviews和drawRect的原因:
假設我們採用的是直接調用drawRect的機制,先考慮這樣一個問題,,現在有兩個UIViewController A和B,A為當前view的viewController,如果此時在A中調用[B.view drawRect],這樣B的view無論如何都會調用drawRect的方法重新繪製一遍,這樣問題就出來了,有必要嗎,畢竟現在顯示的是A中的view!B重新繪製一遍就調用了drawRect中的方法,完全是在浪費系統資源啊,而通過setNeedsDisplay,ios就會很聰明的判斷出 不需要調用drawRect的方法,這樣就避免了系統的無用操作!同理,setNeedsLayout也採用了同樣的機制來!
何時會調用layoutSubviews,何時需要重寫layoutSubviews:
開發人員何時需要重寫layoutSubviews的問題,Apple是這麼解釋的:如果自動適配(autosizing),或自動布局(autolayout)不能滿足你的要求的時候,就需要重寫了。假設一個螢幕從豎屏變為橫屏,本來圖文單元是圖片在上,文字在下面,可能就變為了左右結構;這個是無論自動適配還是自動布局都解決不了,就只能重寫layoutSubviews函數了。
但有時候它會被莫名奇妙的調用,根據國外社區一個人文章,做了總結性翻譯。layoutSubviews在以下情況下會被調用:
1、init初始化不會觸發layoutSubviews
2、addSubview會觸發layoutSubviews
3、設定view的Frame會觸發layoutSubviews,當然前提是frame的值設定前後發生了變化
4、滾動一個UIScrollView會觸發layoutSubviews
5、旋轉Screen會觸發父UIView上的layoutSubviews事件
6、改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件
個人不足:因為在日常工作中我從沒需要重寫過layoutSubviews和drawRect,最複雜的情況無非用幾個view複合在一起就能解決,或者找第三方的代碼,所以對實際中為什麼要自己調用和重寫函數的原因還是不清楚。但目前的這點知識至少可以幫我理解第三方代碼了。
此外,這是我在改寫的代碼,大家可以下載,針對樣本進行研究https://github.com/HeshamMegid/HMSegmentedControl
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
UIView中的layoutSubviews和drawRect