1. 相關(guān)API
基于觸發(fā)約束的布局 Triggering Constraint-Based Layout
-- (BOOL)needsUpdateConstraints
constraint-based layout system使用此返回值去決定是否需要 調(diào)用updateConstraints
作為正常布局過程的一部分翁潘。
- (void)setNeedsUpdateConstraints
當(dāng)一個(gè)自定義view的某個(gè)屬性發(fā)生改變隔缀,并且可能影響到constraint時(shí)隧土,需要調(diào)用此方法去標(biāo)記constraints需要在未來的某個(gè)點(diǎn)更新岖免,系統(tǒng)然后調(diào)用updateConstraints
.
- (void)updateConstraints
更新約束取刃,自定義view應(yīng)該重寫此方法在其中建立constraints. 注意:要在實(shí)現(xiàn)在最后調(diào)用 [super updateConstraints]
- (void)updateConstraintsIfNeeded
立即觸發(fā)約束更新饥努,自動(dòng)更新布局矛缨。
鋪設(shè)子視圖 Laying out Subviews
-(void)layoutSubviews
如果你需要更精確控制子view而昨,而不是使用限制或autoresizing行為帆焕,就需要實(shí)現(xiàn)該方法惭婿。
-(void)layoutIfNeeded
使用此方法強(qiáng)制立即進(jìn)行l(wèi)ayout,從當(dāng)前view開始,此方法會(huì)遍歷整個(gè)view層次(包括superviews)請(qǐng)求layout叶雹。因此财饥,調(diào)用此方法會(huì)強(qiáng)制整個(gè)view層次布局。
- (void)setNeedsLayout
此方法會(huì)將view當(dāng)前的layout設(shè)置為無效的折晦,并在下一個(gè)upadte cycle里去觸發(fā)layout更新钥星。
繪畫和更新視圖
-(void)drawRect:(CGRect)rect
如果你的View畫自定義的內(nèi)容,就要實(shí)現(xiàn)該方法满着,否則避免覆蓋該方法谦炒。
-void)setNeedsDisplay
標(biāo)記整個(gè)視圖的邊界矩形需要重繪
-(void)setNeedsDisplayInRect:(CGRect)invalidRect
標(biāo)記在指定區(qū)域內(nèi)的視圖的邊界需要重繪
相關(guān)聯(lián)的改變 Observing View-Related Changes
-(void)didAddSubview:(UIView *)subview
通知視圖指定子視圖已經(jīng)添加
-(void)willRemoveSubview:(UIView *)subview
通知視圖將要移除指定的子視圖
-(void)willMoveToSuperview:(UIView *)newSuperview
通知視圖將要移動(dòng)到一個(gè)新的父視圖中
-(void)didMoveToSuperview
通知視圖已經(jīng)移動(dòng)到一個(gè)新的父視圖中
-(void)willMoveToWindow:(UIWindow *)newWindow
通知視圖將要移動(dòng)到一個(gè)新的window中
-(void)didMoveToWindow
通知視圖已經(jīng)移動(dòng)到一個(gè)新的window中
配置自動(dòng)調(diào)整大小狀態(tài) Configuring the Resizing Behavior
-(void)sizeToFit
根據(jù)子視圖的大小位置,調(diào)整視圖风喇,使其恰好圍繞子視圖宁改, 也就是說自動(dòng)適應(yīng)子視圖的大小,只顯示子視圖
-(CGSize)sizeThatFits:(CGSize)size
讓視圖計(jì)算最適合子視圖的大小魂莫,即能把全部子視圖顯示出來所需要的最小的size
2. Auto Layout Process 自動(dòng)布局過程
布局過程
布局過程與使用springs and struts(autoresizingMask)比較还蹲,Auto layout在view顯示之前,多引入了兩個(gè)步驟:updating constraints
和laying out views
。每一個(gè)步驟都依賴于上一個(gè)谜喊。display
依賴layout
潭兽,而layout依賴updating constraints
。 updating constraints->layout->display
第一步:updating constraints
斗遏,被稱為測(cè)量階段山卦,其從下向上(from subview to super view),為下一步layout準(zhǔn)備信息∽钜祝可以通過調(diào)用方法setNeedUpdateConstraints去觸發(fā)此步怒坯。constraints的改變也會(huì)自動(dòng)的觸發(fā)此步。但是藻懒,當(dāng)你自定義view的時(shí)候,如果一些改變可能會(huì)影響到布局的時(shí)候视译,通常需要自己去通知Auto layout
嬉荆,updateConstraintsIfNeeded
。自定義view的話酷含,通潮稍纾可以重寫updateConstraints
方法,在其中可以添加view需要的局部的contraints椅亚。
第二步:layout限番,其從上向下(from super view to subview),此步主要應(yīng)用上一步的信息去設(shè)置view的center和bounds呀舔∶峙埃可以通過調(diào)用setNeedsLayout去觸發(fā)此步驟,此方法不會(huì)立即應(yīng)用layout媚赖。如果想要系統(tǒng)立即的更新layout霜瘪,可以調(diào)用layoutIfNeeded
。另外惧磺,自定義view可以重寫方法layoutSubViews來在layout的工程中得到更多的定制化效果颖对。
第三步:display,此步時(shí)把view渲染到屏幕上磨隘,它與你是否使用Auto layout無關(guān)缤底,其操作是從上向下(from super view to subview),通過調(diào)用setNeedsDisplay
觸發(fā)番捂,setNeedsDisplay觸發(fā)系統(tǒng)會(huì)調(diào)用UIView 的 drawRect方法个唧。因?yàn)槊恳徊蕉家蕾嚽耙徊剑虼艘粋€(gè)display可能會(huì)觸發(fā)layout白嘁,當(dāng)有任何layout沒有被處理的時(shí)候坑鱼,同理,layout可能會(huì)觸發(fā)updating constraints,當(dāng)constraint system更新改變的時(shí)候鲁沥。
需要注意的是呼股,這三步不是單向的,constraint-based layout是一個(gè)迭代的過程画恰,layout過程中彭谁,可能去改變constraints,又一次觸發(fā)updating constraints允扇,進(jìn)行一輪layout過程缠局。
下面說說UIViewController的布局過程
VC的生命周期的部分過程viewDidLoad -> viewWillAppear -> updateViewConstraints -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidAppear -> viewWillDisAppear -> updateViewConstraints -> viewDidDisAppear
對(duì)應(yīng)updateConstraints -> layoutSubViews -> drawRect
當(dāng)view修改約束(addConstraint, removeConstraint)會(huì)觸發(fā)setNeedsUpdateConstraints
考润,而這個(gè)在layoutSubViews之前會(huì)觸發(fā)updateConstraints狭园,完成之后會(huì)調(diào)用layoutSubViews。
UIViewController在有個(gè)updateViewConstraints
方法糊治,這個(gè)方法實(shí)際是self.view 被設(shè)置了setNeedsUpdateConstraints
(第一次展示的時(shí)候)唱矛,必然會(huì)調(diào)用這個(gè)方法(與上面的解釋保持一致了,第一次可以理解為為self.view增加了各種約束)井辜。而這個(gè)方法的默認(rèn)實(shí)現(xiàn)是調(diào)用子view的updateConstraints
方法绎谦,這樣就自上而下的完成了布局。
此處需要注意的地方:
- 不要忘記調(diào)用父類的方法粥脚,避免有時(shí)候出現(xiàn)一些莫名的問題窃肠。
- 在view的layoutSubViews或者ViewController的
viewDidLayoutSubviews
方法里后可以拿到view的實(shí)際frame,所以當(dāng)我們真的需要frame的時(shí)候需要在這個(gè)時(shí)間點(diǎn)以后才能拿到刷允。 - 下面我們可以解釋是為什么
viewDidLoad
里通過setFrame的方式改過原先在storyboard里拖動(dòng)的約束代碼無效了冤留。因?yàn)?code>updateViewConstraints在viewDidLoad
后執(zhí)行,會(huì)覆蓋掉之前的設(shè)置的frame恃锉,所以無效搀菩。
3. 補(bǔ)充總結(jié)
以下情況下會(huì)調(diào)用layoutSubviews
:
- init初始化不會(huì)觸發(fā)
layoutSubviews
,但是是用initWithFrame
進(jìn)行初始化時(shí)破托,當(dāng)rect的值不為CGRectZero時(shí),也會(huì)觸發(fā)肪跋。 -
addSubview
會(huì)觸發(fā)layoutSubviews
- 設(shè)置view的Frame會(huì)觸發(fā)
layoutSubviews
,當(dāng)然前提是frame的值設(shè)置前后發(fā)生了變化 - 滾動(dòng)一個(gè)UIScrollView會(huì)觸發(fā)
layoutSubviews
- 旋轉(zhuǎn)Screen會(huì)觸發(fā)父UIView上的
layoutSubviews
事件 - 改變一個(gè)UIView大小的時(shí)候也會(huì)觸發(fā)父UIView上的layoutSubviews事件
刷新子對(duì)象布局
-layoutSubviews
方法:這個(gè)方法土砂,默認(rèn)沒有做任何事情州既,需要子類進(jìn)行重寫
-setNeedsLayout
方法: 標(biāo)記為需要重新布局,異步調(diào)用layoutIfNeeded刷新布局萝映,不立即刷新吴叶,但layoutSubviews一定會(huì)被調(diào)用
-layoutIfNeeded
方法:如果,有需要刷新的標(biāo)記序臂,立即調(diào)用layoutSubviews進(jìn)行布局(如果沒有標(biāo)記蚌卤,不會(huì)調(diào)用layoutSubviews)如果要立即刷新实束,要先調(diào)用[view setNeedsLayout]
,把標(biāo)記設(shè)為需要布局逊彭,然后馬上調(diào)用[view layoutIfNeeded]
咸灿,實(shí)現(xiàn)布局在視圖第一次顯示之前,標(biāo)記總是“需要刷新”的侮叮,可以直接調(diào)用[view layoutIfNeeded]
重繪
-drawRect:(CGRect)rect
方法:重寫此方法避矢,執(zhí)行重繪任務(wù)
-setNeedsDisplay
方法:標(biāo)記為需要重繪,異步調(diào)用drawRect
-setNeedsDisplayInRect:(CGRect)invalidRect
方法:標(biāo)記為需要局部重繪
sizeToFit
會(huì)自動(dòng)調(diào)用sizeThatFits
方法囊榜;
sizeToFit
不應(yīng)該在子類中被重寫审胸,應(yīng)該重寫sizeThatFits
sizeThatFits
傳入的參數(shù)是receiver當(dāng)前的size,返回一個(gè)適合的sizesizeToFit
可以被手動(dòng)直接調(diào)用sizeToFit
和sizeThatFits
方法都沒有遞歸卸勺,對(duì)subviews也不負(fù)責(zé)砂沛,只負(fù)責(zé)自己
layoutSubviews
對(duì)subviews重新布局
layoutSubviews
方法調(diào)用先于drawRectsetNeedsLayout
在receiver標(biāo)上一個(gè)需要被重新布局的標(biāo)記,在系統(tǒng)runloop的下一個(gè)周期自動(dòng)調(diào)用layoutSubviews
layoutIfNeeded
方法如其名曙求,UIKit會(huì)判斷該receiver是否需要layout尺上,根據(jù)Apple官方文檔,layoutIfNeeded
方法應(yīng)該是這樣的layoutIfNeeded
遍歷的不是superview鏈,應(yīng)該是subviews鏈drawRect
是對(duì)receiver的重繪圆到,能獲得context。
總之卑吭,理解view布局的過程芽淡,可以幫助你理解View顯示的相關(guān)問題,解決一些界面問題豆赏,合理使用以上方法對(duì)你自定義控件也有很大的幫助挣菲。