一、首先介紹幾組api
1)基于觸發限制的布局 Triggering Constraint-Based Layout
constraint-based layout system使用此傳回值去決定是否需要 調用updateConstraints作為正常布局過程的一部分。
- (void)setNeedsUpdateConstraints
當一個自定義view的某個屬性發生改變,并且可能影響到constraint時,需要調用此方法去标記constraints需要在未來的某個點更新,系統然後調用updateConstraints.
- (void)updateConstraints
更新限制,自定義view應該重寫此方法在其中建立constraints. 注意:要在實作在最後調用[super updateConstraints]
- (void)updateConstraintsIfNeeded
立即觸發限制更新,自動更新布局。
2)鋪設子視圖 Laying out Subviews
如果你需要更精确控制子view,而不是使用限制或autoresizing行為,就需要實作該方法。
使用此方法強制立即進行layout,從目前view開始,此方法會周遊整個view層次(包括superviews)請求layout。是以,調用此方法會強制整個view層次布局。
- (void)setNeedsLayout
此方法會将view目前的layout設定為無效的,并在下一個upadte cycle裡去觸發layout更新。
3)繪畫和更新視圖 Drawing and Updati
如果你的View畫自定義的内容,就要實作該方法,否則避免覆寫該方法。
- void)setNeedsDisplay
标記整個視圖的邊界矩形需要重繪
标記在指定區域内的視圖的邊界需要重繪
相關聯的改變 Observing View-Related Changes
通知視圖指定子視圖已經添加
通知視圖将要移除指定的子視圖
通知視圖将要移動到一個新的父視圖中
通知視圖已經移動到一個新的父視圖中
通知視圖将要移動到一個新的window中
通知視圖已經移動到一個新的window中
5)配置自動調整大小狀态 Configuring the Resizing Behavior
根據子視圖的大小位置,調整視圖,使其恰好圍繞子視圖, 也就是說自動适應子視圖的大小,隻顯示子視圖
讓視圖計算最适合子視圖的大小,即能把全部子視圖顯示出來所需要的最小的size
二、Auto Layout Process 自動布局過程
1)
布局過程
布局過程與使用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準備資訊。可以通過調用方法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觸發,setNeedsDisplay觸發系統會調用UIView 的 drawRect方法。
因為每一步都依賴前一步,是以一個display可能會觸發layout,當有任何layout沒有被處理的時候,同理,layout可能會觸發updating constraints,當constraint system更新改變的時候。
需要注意的是,這三步不是單向的,constraint-based layout是一個疊代的過程,layout過程中,可能去改變constraints,又一次觸發updating constraints,進行一輪layout過程。
下面說說UIViewController的布局過程
VC的生命周期的部分過程
viewDidLoad -> viewWillAppear -> updateViewConstraints -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidAppear -> viewWillDisAppear -> updateViewConstraints -> viewDidDisAppear
對應updateConstraints -> layoutSubViews -> drawRect
當view修改限制(addConstraint, removeConstraint)會觸發setNeedsUpdateConstraints,而這個在layoutSubViews之前會觸發updateConstraints,完成之後會調用layoutSubViews。UIViewController在有個updateViewConstraints 方法,這個方法實際是self.view 被設定了setNeedsUpdateConstraints(第一次展示的時候),必然會調用這個方法(與上面的解釋保持一緻了,第一次可以了解為為self.view增加了各種限制)。而這個方法的預設實作是調用子view的updateConstraints方法,這樣就自上而下的完成了布局。
此處需要注意的地方:
1. 不要忘記調用父類的方法,避免有時候出現一些莫名的問題。
2. 在view的layoutSubViews或者ViewController的viewDidLayoutSubviews方法裡後可以拿到view的實際frame,是以當我們真的需要frame的時候需要在這個時間點以後才能拿到。
下面我們可以解釋是為什麼viewDidLoad裡通過setFrame的方式改過原先在storyboard裡拖動的限制代碼無效了。因為updateViewConstraints在viewDidLoad後執行,會覆寫掉之前的設定的frame,是以無效。
三、補充
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方法:這個方法,預設沒有做任何事情,需要子類進行重寫
-setNeedsLayout方法: 标記為需要重新布局,異步調用layoutIfNeeded重新整理布局,不立即重新整理,但layoutSubviews一定會被調用
-layoutIfNeeded方法:如果,有需要重新整理的标記,立即調用layoutSubviews進行布局(如果沒有标記,不會調用layoutSubviews)
如果要立即重新整理,要先調用[view setNeedsLayout],把标記設為需要布局,然後馬上調用[view layoutIfNeeded],實作布局
在視圖第一次顯示之前,标記總是“需要重新整理”的,可以直接調用[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。
以上這些進一步驗證了上面的說法。
總之,了解view布局的過程,可以幫助你了解View顯示的相關問題,解決一些界面問題,合理使用以上方法對你自定義控件也有很大的幫助。