最近開(kāi)始看WWDC的視頻恐仑,復(fù)習(xí)了一下AutoLayout的基礎(chǔ)內(nèi)容墓赴,寫(xiě)一點(diǎn)兒總結(jié)吧,免得看完又忘嘱丢。
AutoLayout
AutoLayout是基于約束的赚哗,描述性的布局系統(tǒng)她紫。使用約束來(lái)描述布局,view的frames會(huì)依據(jù)這些約束自動(dòng)進(jìn)行計(jì)算屿储。
AutoLayout是蘋(píng)果在iOS6中引入的用來(lái)替換之前的“Springs&Struts”布局模型的新布局系統(tǒng)。
“Spring&Struts”是基于frame的布局渐逃,它在大部分情況下還是有用的够掠,但是隨著4寸iPhone5的發(fā)布帶來(lái)的大屏適配的工作以及在橫豎屏切換時(shí)經(jīng)常需要在
viewWillLayoutSubviews
方法中編寫(xiě)大量布局代碼。相比之下茄菊,AutoLayout不僅可以完成“Spring&Struts”提供的功能疯潭,還提供了其所沒(méi)有的特性:
- AutoLayout可以指定任意兩個(gè)view的相對(duì)位置赊堪,而不需要像Autoresizing Mask那樣需要兩個(gè)view在直系的view hierarchy中。
- AutoLayout不必須指定相等關(guān)系的約束竖哩,它可以指定非相等約束(大于或者小于等)哭廉,而Autoresizing Mask所能做的布局只能是相等條件的。
-
AutoLayout可以指定約束的優(yōu)先級(jí)相叁,計(jì)算frame時(shí)將優(yōu)先按照滿足優(yōu)先級(jí)高的條件進(jìn)行計(jì)算遵绰。
如何添加約束
通常我們直接在IB中設(shè)置約束,這里著重記錄下如何用代碼添加約束:
當(dāng)在代碼中創(chuàng)建視圖和它們的約束條件時(shí)候增淹,一定要記得將 translatesAutoResizingMaskIntoConstraints
屬性設(shè)置為 NO椿访。
- 我們可以使用
NSLayoutConstraint
的類方法:+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c
創(chuàng)建約束對(duì)象。
- 注意:上面方程的等號(hào)表示的是相等關(guān)系虑润,而不是賦值成玫。當(dāng)自動(dòng)布局求解這些方程時(shí),并不是將等式右邊的值賦值給等式左邊拳喻。相反哭当,它同時(shí)計(jì)算屬性 1 和屬性 2 的值使它們之間的關(guān)系成立。
- 使用
UIView
對(duì)象的實(shí)例方法:-(void)addConstraint:(NSLayoutConstraint *)constraint
將約束添加到view上冗澈。
將約束添加到view上時(shí)要注意:
-
對(duì)于兩個(gè)同層級(jí)view之間的約束關(guān)系钦勘,添加到他們的父view上
-
對(duì)于兩個(gè)不同層級(jí)view之間的約束關(guān)系,添加到他們最近的共同父view上
-
對(duì)于有層次關(guān)系的兩個(gè)view之間的約束關(guān)系渗柿,添加到層次較高的父view上
- 可以通過(guò)
-setNeedsUpdateConstraints
(在下一次繪制循環(huán)中觸發(fā)重新布局)和-layoutIfNeeded
(強(qiáng)制系統(tǒng)立即更新視圖樹(shù)的布局)兩個(gè)方法來(lái)刷新約束的改變个盆,使UIView重新布局。這和CoreGraphic的-setNeedsDisplay
一套東西是一樣的朵栖。
布局過(guò)程
- 更新約束:這是自下而上(從子視圖到父視圖)發(fā)生的颊亮,它為布局準(zhǔn)備好必要的信息,而這些布局將在實(shí)際設(shè)置視圖的 frame 時(shí)被傳遞過(guò)去并被使用陨溅。你可以通過(guò)調(diào)用
-setNeedsUpdateConstraints
來(lái)觸發(fā)這個(gè)操作终惑,同時(shí),你對(duì)約束條件系統(tǒng)做出的任何改變都將自動(dòng)觸發(fā)這個(gè)方法门扇。 - 布局:這是個(gè)自上而下(從父視圖到子視圖)的過(guò)程雹有,這種布局操作實(shí)際上是通過(guò)設(shè)置 frame(在 OS X 中)或者 center 和 bounds(在 iOS 中)將約束條件系統(tǒng)的解決方案應(yīng)用到視圖上。你可以通過(guò)調(diào)用
-setNeedsLayout
來(lái)觸發(fā)一個(gè)操作請(qǐng)求臼寄,這并不會(huì)立刻應(yīng)用布局霸奕,而是在稍后再進(jìn)行處理。因?yàn)樗械牟季终?qǐng)求將會(huì)被合并到一個(gè)布局操作中去吉拳,所以你不需要為經(jīng)常調(diào)用這個(gè)方法而擔(dān)心质帅。 - 顯示:顯示器都會(huì)自上而下將渲染后的視圖傳遞到屏幕上,你也可以通過(guò)調(diào)用
-setNeedsDisplay
來(lái)觸發(fā),這將會(huì)導(dǎo)致所有的調(diào)用都被合并到一起推遲重繪煤惩。重寫(xiě)熟悉的 drawRect:能夠讓我們獲得自定義視圖中顯示過(guò)程的所有權(quán)嫉嘀。
每一步都是依賴前一步操作的,如果有任何布局的變化還沒(méi)實(shí)行的話,顯示操作將會(huì)觸發(fā)一個(gè)布局行為魄揉。類似地剪侮,如果約束條件系統(tǒng)中存在沒(méi)有實(shí)行的改變,布局變化也將會(huì)觸發(fā)更新約束條件洛退。
但是這三步并不是單向的瓣俯。基于約束條件的布局是一個(gè)迭代的過(guò)程不狮,布局操作可以基于之前的布局方案來(lái)對(duì)約束做出更改降铸,而這將再次觸發(fā)約束的更新,并緊接另一個(gè)布局操作摇零。這可以被用來(lái)創(chuàng)建高級(jí)的自定義視圖布局推掸,但是如果你每一次調(diào)用的自定義 -layoutSubviews
都會(huì)導(dǎo)致另一個(gè)布局操作的話,你將會(huì)陷入到無(wú)限循環(huán)的麻煩中去驻仅。
控制布局
- 通常我們需要在
-updateConstraints
方法中集中添加約束谅畅,并且確保在你的實(shí)現(xiàn)中增加任何你需要布局子視圖的約束條件之后,調(diào)用一下[super updateConstraints]
噪服,在這個(gè)方法中毡泻,你不會(huì)被允許禁用何約束條件,因?yàn)槟阋呀?jīng)進(jìn)入上面所描述的布局過(guò)程的第一步了粘优。
如果稍后一個(gè)失效的約束條件發(fā)生了改變的話仇味,你需要立刻移除這個(gè)約束并調(diào)用-setNeedsUpdateConstraints
事實(shí)上,僅在這種情況下你需要觸發(fā)更新約束條件的操作雹顺。 - 如果你不能利用布局約束條件達(dá)到子視圖預(yù)期的布局丹墨,你可以進(jìn)一步重寫(xiě)
-layoutSubviews
,通過(guò)這種方式當(dāng)約束條件系統(tǒng)得到解決并且結(jié)果將要被應(yīng)用到視圖中時(shí),你便已經(jīng)進(jìn)入到布局過(guò)程的第二步了嬉愧。
Intrinsic Content Size
一些視圖依據(jù)給定的內(nèi)容有一個(gè)原生的大小贩挣,這就稱為它們的固有內(nèi)容尺寸。并不是所有視圖都有固有內(nèi)容尺寸:
為了在自定義視圖中實(shí)現(xiàn)固有內(nèi)容尺寸没酣,你需要做兩件事:重寫(xiě)
-intrinsicContentSize
為內(nèi)容返回恰當(dāng)?shù)拇笮⊥醪疲瑹o(wú)論何時(shí)有任何會(huì)影響固有內(nèi)容尺寸的改變發(fā)生時(shí),調(diào)用 invalidateIntrinsicContentSize
裕便。如果這個(gè)視圖只有一個(gè)方向的尺寸設(shè)置了固有尺寸绒净,那么為另一個(gè)方向的尺寸返回 UIViewNoIntrinsicMetric/NSViewNoIntrinsicMetric
。
注意當(dāng)為了填充一個(gè)空間需要拉伸所有視圖時(shí)偿衰,如垂直擺放的兩個(gè)textview疯溺,如果它們都有一個(gè)相同的內(nèi)容壓縮優(yōu)先級(jí)论颅,這個(gè)布局就會(huì)是有歧義的哎垦。自動(dòng)布局不知道該拉伸哪個(gè)視圖囱嫩,這時(shí)就應(yīng)該調(diào)整某個(gè)textview的content-hugging priority與 compression-resistance priority。
Alignment Rect
自動(dòng)布局并不會(huì)操作視圖的 frame漏设,但能作用于視圖的 alignment rect墨闲,在很多情況下,它們是相同的郑口。你可以通過(guò)重寫(xiě)
alignmentRectForFrame:
和 frameForAlignmentRect
這兩個(gè)方法在frame與alignment rect之間轉(zhuǎn)換鸳碧。
當(dāng)你要自定義一個(gè)控件的時(shí)候可以重寫(xiě)
alignmentRectInsets
方法,這個(gè)方法允許你返回相對(duì)于 frame 的 edge insets犬性。
Debugging
對(duì)于不能確定的布局瞻离,可以通過(guò)調(diào)試時(shí)暫停程序,在debugger中輸入
(lldb)po [[UIWindow keyWindow] _autolayoutTrace]
來(lái)遍歷視圖層乒裆,檢查錯(cuò)誤套利。檢查是否有ambiguity:
(lldb)po [view hasAmbiguousLayout]
哪里發(fā)生了ambiguous:
(lldb)po [view excerciseAmbiguousInLayout]
動(dòng)畫(huà)
- CoreAnimation:
[UIView animateWithDuration:0.5 animations:^{
[view layoutIfNeeded];
}];
請(qǐng)注意,使用這種方法鹤耍,你可以對(duì)約束條件做出的改變并不局限于約束條件的常量肉迫。你可以刪除約束條件,增加約束條件稿黄,甚至使用臨時(shí)動(dòng)畫(huà)約束條件喊衫。由于新的約束只被解釋一次來(lái)決定新的 frames,所以更復(fù)雜的布局改變都是有可能的杆怕。
需要記住的是:Core Animation 和 Auto Layout 結(jié)合在一起產(chǎn)生視圖動(dòng)畫(huà)時(shí)族购,自己不要接觸視圖的 frame。一旦視圖使用自動(dòng)布局陵珍,那么你已經(jīng)將設(shè)置 frame 的責(zé)任交給了布局系統(tǒng)寝杖。你的干擾將造成怪異的行為。
- 直接對(duì)約束條件做動(dòng)畫(huà)
約束條件一旦創(chuàng)建后撑教,只有其常量可以被改變朝墩。
UIStackView
StackView是iOS9中提供的一種簡(jiǎn)單布局控件,剔除了復(fù)雜的約束伟姐,利用自動(dòng)布局的強(qiáng)大來(lái)布局界面收苏,單個(gè) StackView 由一行或者一列控件組成,StackView 根據(jù)設(shè)置的對(duì)齊愤兵,間距和大小屬性來(lái)決定subviews的位置鹿霸。
同時(shí),StackView也可嵌套來(lái)構(gòu)建更復(fù)雜的布局秆乳。
AutoLayout和UIScrollView
在使用scroll view時(shí)懦鼠,不僅要定義它的大小跟位置钻哩,還要確定它的內(nèi)容的大小肛冶!可按如下步驟來(lái)對(duì)scroll view進(jìn)行布局:
Your layout must fully define the size of the content view (except where defined in steps 5 and 6).To set the height based on the intrinsic size of your content, you must have an unbroken chain of constraints and views stretching from the content view’s top edge to its bottom edge.
If your content does not have an intrinsic content size, you must add the appropriate size constraints, either to the content view or to the content.
AutoLayout和Self-Sizing Table View Cells
在iOS8之后街氢,可以下面這種簡(jiǎn)單的方式來(lái)對(duì)動(dòng)態(tài)變化的Table View Cell的高度進(jìn)行自動(dòng)計(jì)算:
tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableViewAutomaticDimension
當(dāng)然,前提是你的cell中的內(nèi)容的約束已經(jīng)從上至下形成一個(gè)閉環(huán)睦袖。AutoLayout可由此可以推算出cell的高度珊肃。
參考資料
- WWDC 2012 Session 202 Introduction to Auto Layout for iOS and OS X
- WWDC 2012 Session 228 Best Practices for Mastering Auto Layout
- WWDC 2012 Session 232 Auto Layout by Example
- WWDC 2012 Session筆記——202, 228, 232 AutoLayout(自動(dòng)布局)入門
- 先進(jìn)的自動(dòng)布局工具箱
- Auto Layout Guide
- Auto Layout Tutorial in iOS 9 Part 1: Getting Started
- Auto Layout Tutorial in iOS 9 Part 2: Constraints