一、updateViewConstraints
- (void)updateViewConstraints NS_AVAILABLE_IOS(6_0);
UIViewController中也新增了一個更新佈局約束的方法,在AutoLayoutUIView相關API的筆記中,詳細講述了UIView的一組更新佈局約束的方法。
這個方法默認的實現是調用對應View的
-updateConstraints 。ViewController的View在更新視圖佈局時,會先調用ViewController的updateViewConstraints 方法。我們可以通過重寫這個方法去更新當前View的內部佈局,而不用再繼承這個View去重寫-updateConstraints方法。我們在重寫這個方法時,務必要調用 super 或者 調用當前View的-updateConstraints 方法。
通過代碼爲xib或sb中view增加約束時,儘量避免在viewDidLoad中執行,最好放在updateViewConstraints [UIViewController]或者updateConstraints[UIView]中,記得調用[super updateViewConstraints]或者[super updateConstraints];
- (void)updateViewConstraints
{
// 在這裏爲你的view添加約束,請確保該view的translatesAutoresizingMaskIntoConstraints屬性已設置爲NO
[super updateViewConstraints];
}
如果你真的寫在viewDidLoad裏了,那麼可能會遇到這種崩潰錯誤。
Terminating app due to uncaught exception"NSInternalInconsistencyException"
二、layoutSubviews在以下情況下會被調用:
1、init初始化不會觸發layoutSubviews
但是是用initWithFrame 進行初始化時,當rect的值不爲CGRectZero時,也會觸發
2、addSubview會觸發layoutSubviews
3、設置view的Frame會觸發layoutSubviews,當然前提是frame的值設置前後發生了變化
4、滾動一個UIScrollView會觸發layoutSubviews
5、旋轉Screen會觸發父UIView上的layoutSubviews事件
6、改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件
layoutSubviews, 當我們在某個類的內部調整子視圖位置時,需要調用。 反過來的意思就是說:如果你想要在外部設置subviews的位置,就不要重寫。
三、刷新子對象佈局
-layoutSubviews方法:這個方法,默認沒有做任何事情,需要子類進行重寫
-setNeedsLayout方法:標記爲需要重新佈局,異步調用layoutIfNeeded刷新佈局,不立即刷新,但layoutSubviews一定會被調用
-layoutIfNeeded方法:如果,有需要刷新的標記,立即調用layoutSubviews進行佈局(如果沒有標記,不會調用layoutSubviews)。
如果要立即刷新,要先調用[view setNeedsLayout],把標記設爲需要佈局,然後馬上調用[view layoutIfNeeded],實現佈局
四、重繪
-
drawRect:(CGRect)rect方法:重寫此方法,執行重繪任務 ;
-setNeedsDisplay方法:標記爲需要重繪,異步掉用drawRect;
-setNeedsDisplayInRect:(CGRect)invalidRect方法:標記爲需要局部重繪;
sizeToFit會自動調用sizeThatFits方法;
sizeToFit不應該在子類中被重寫,應該重寫sizeThatFits;
sizeThatFits傳入的參數是receiver當前的size,返回一個適合的size;
sizeToFit可以被手動直接調用;
sizeToFit和sizeThatFits方法都沒有遞歸,對subviews也不負責,只負責自己。
layoutSubviews對subviews重新佈局;
layoutSubviews方法調用先於drawRect;
setNeedsLayout在receiver標上一個需要被重新佈局的標記,在系統runloop的下一個週期自動調用layoutSubviews
layoutIfNeeded方法如其名,UIKit會判斷該receiver是否需要layout.根據Apple官方文檔,layoutIfNeeded方法應該是這樣的。
layoutIfNeeded遍歷的不是superview鏈,應該是subviews鏈;
drawRect是對receiver的重繪,能獲得context。
setNeedDisplay在receiver標上一個需要被重新繪圖的標記,在下一個draw週期自動重繪,iphone device的刷新頻率是60hz,也就是1/60秒後重繪。
五、setNeedsUpdateConstraints,needsUpdateConstraints等基於約束的AutoLayer的方法
1、setNeedsUpdateConstraints
當一個自定義view的某個屬性發生改變,並且可能影響到constraint時,需要調用此方法去標記constraints需要在未來的某個點更新,系統然後調用updateConstraints.
2、needsUpdateConstraints
constraint-based layout system使用此返回值去決定是否需要調用updateConstraints作爲正常佈局過程的一部分。
3、updateConstraintsIfNeeded
立即觸發約束更新,自動更新佈局。
4、updateConstraints
自定義view應該重寫此方法在其中建立constraints.注意:要在實現在最後調用[super updateConstraints]
Auto Layout Process 自動佈局過程
與使用springs and struts(autoresizingMask)比較,Auto layout在view顯示之前,多引入了兩個步驟:updating constraints 和laying out views。每一個步驟都依賴於上一個。
display依賴layout,而layout依賴updating constraints。 updating constraints->layout->display
第一步:updating constraints,被稱爲測量階段,其從下向上(fromsubview to super view),爲下一步layout準備信息。可以通過調用方法setNeedUpdateConstraints去觸發此步。constraints的改變也會自動的觸發此步。但是,當你自定義view的時候,如果一些改變可能會影響到佈局的時候,通常需要自己去通知Autolayout,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 tosubview),通過調用setNeedsDisplay觸發,
因爲每一步都依賴前一步,因此一個display可能會觸發layout,當有任何layout沒有被處理的時候,同理,layout可能會觸發updating constraints,當constraint system更新改變的時候。
需要注意的是,這三步不是單向的,constraint-based layout是一個迭代的過程,layout過程中,可能去改變constraints,有一次觸發updating constraints,進行一輪layout過程。
注意:如果你每一次調用自定義layoutSubviews都會導致另一個佈局傳遞,那麼你將會陷入一個無限循環中。
六、AutoLayout相關的幾個易混淆的方法
l setNeedsLayout
l layoutIfNeeded
l layoutSubViews
l setNeedsUpdateConstraints
l updateConstraitsIfNeed
l updateConstraints
子視圖在界面上的顯示大概經過了:更新約束-通過約束依賴關係得到具體的frame-展示到界面。上面幾個是和autolayout相關的方法,有必要大概瞭解一下這些方法具體是怎麼用的以及在什麼情況下觸發。
1.[layoutView setNeedsUpdateConstraints]:告訴layoutView需要更新約束,在下次計算或者更新約束會更新約束
2.[layoutView updateConstraintsIfNeeded]:告訴layoutView立即更新約束,
3.updateConstraints:系統更新約束的實際方法
總結上面的3點就是,setNeedsUpdateConstraints
確保了在將來某一時刻調用updateConstraintsIfNeeded
之後會接着調用updateConstraints
,從而達到更新view的約束的目的。但是要注意的是,如果僅僅單獨調用2,不一定能夠保證會調用updateConstraints
,因爲如果view上的約束是沒有變動的且沒有標記需要update的,這時就不會調用updateConstraints
。
4.[layoutView setNeedsLayout]:告訴layoutView頁面需要更新,但不立即執行
5.[layoutView layoutIfNeeded]:告訴layoutView頁面佈局立即更新
6.layoutSubviews:系統重寫佈局的實際方法
總結以上3點,setNeedsLayout
確保了在將來某個時刻通過調用layoutIfNeeded
之後會調用系統的layoutSubviews
,從而重寫對view重新佈局。同樣的如果單獨調用5,不一定能夠保證調用layoutSubviews
。[注:筆者寫了個demo發現,調用setNeedsLayout
會直接調用layoutSubviews
]。如果想要每次都能立即更新佈局,那就要把兩個方法一起用,同樣也適用於1和2。
系統調用layoutSubViews
時,就會調用updateConstraintsIfNeeded
,通過更新約束,用superView到subView的層次順序,來計算frame,反向確定佈局。
stackoverflow上有關於上面幾個方法的深入解答並分享了作者的實用經驗:
- 如果僅想要立即改變約束,調用
setNeedsLayout
- 如果改變view的一些屬性(如
offsets
)可能會導致佈局的改變,那麼調用setNeedsUpdateConstraints
,更多的時候後面需要加setNeedsLayout
。 - 如果想要立即改變佈局,如會形成新的frame,那麼需要在調用
layoutIfNeeded
。