iOS學習筆記(5)-Auto Layout基本原理

之前在看MIT那個教學視頻時逗扒,對iOS的界面布局點到即止,一直對Auto Layout的原理不太明了。最近重新看了遍官方的文檔吁伺,終于對Auto Layout明白了一二。本文對iOS8加入的Size Class以及iOS9加入的Stack Views暫時不做過多討論租谈,后續(xù)有時間再補上篮奄,我是剛開始學習iOS開發(fā),難免有理解錯誤的地方割去,請大家指正窟却。

1 UIView的層次結構

在討論Auto Layout前先來了解下UIView的層次結構,在iOS的視圖中呻逆,最底層的是UIWindow(UIWindow當然也是從UIView繼承而來)夸赫,其上再是我們的View Controller的UIView,再上面則是我們自己拖拽的各種控件的UIView咖城。要看到UIView的層次結構茬腿,可以通過Xcode的Debug View HieraHierarchy按鈕來查看。

圖1.1 UIView層次查看按鈕

下面是我創(chuàng)建的一個測試的工程代碼宜雀,選擇的是Single View Application,工程創(chuàng)建好后切平,Xcode就已經為我們創(chuàng)建了一個View Controller(本文后面用VC來指代View Controller),并設置好了VC對應的Class辐董。我在Main.storyboard的VC對應的View上面加入了一個Button和一個Label悴品。

我們可以看到這個測試應用的UIView層次結構如下,一共四層:其中最底層為UIWindow简烘,一個應用通常只有一個UIWindow他匪,它是所有子視圖的根視圖。之上是VC對應的UIView夸研,再上一層就是UILabel和UIButton邦蜜,最上面那層是UIButtonLabel(也就是我們通常見到的 button.titleLabel)。

圖1.2 UIView層次圖

這些UIView的層次關系是:

UIWindow.superview -> null
UIView.superview -> UIWindow
UIButton.superview -> UIView
UILabel.superview -> UIView
UIButtonLabel.superview -> UIButton

2 Frame-based Layout

在談論Auto Layout之前亥至,先看看Auto Layout出現前iOS是通過什么來實現視圖的布局的悼沈。在Auto Layout出現前贱迟,iOS開發(fā)要布局視圖是基于frame的,如在我的筆記1中提到的那樣絮供,即只要指定視圖的起始坐標(origin)以及寬度(width)和高度(height)即可確定視圖在superview中的位置衣吠。如下圖所示,第一個視圖起始坐標為(20,20)壤靶,寬度是120缚俏,高度為80;第二次視圖起始坐標為(20,108),寬度高度與第一個視圖相同:

圖2.1 基于frame的布局

如果在程序運行過程中贮乳,如果有視圖的位置改變忧换,則需要重新計算所有受影響的視圖的位置。通過編碼來實現位置定位固然有很大的靈活性向拆,單頁帶來了很大的不便亚茬,比如我們屏幕尺寸發(fā)生變化,或者旋轉屏幕浓恳,為了保持之前的布局刹缝,就需要修改其中一些視圖的起始位置以及寬度高度等。雖然在UIView中有一個autoresizingMask的屬性颈将,它對應的是一個枚舉的值梢夯,這個屬性能夠自動調整子控件與父控件中間的位置,寬高等晴圾,能夠在一定程度上減輕基于frame布局帶來的不便颂砸,但是autoresizingMask并只支持父子視圖之間進行約束,并不支持同級視圖和跨級視圖的布局疑务。對于復雜的用戶界面同樣需要編碼進行控制。正是由于這些問題梗醇,才誕生了我們這篇文章中要討論的Auto Layout知允。

3 Auto Layout

3.1 Auto Layout基本原理

Auto Layout是一種全新的布局方式,它采用一系列約束(constraints)來實現自動布局叙谨,當你的屏幕尺寸發(fā)生變化或者屏幕發(fā)生旋轉時温鸽,可以不用添加代碼來保持原有布局不變,實現視圖的自動布局手负。

所謂約束涤垫,通常是定義了兩個視圖之間的關系(當然你也可以一個視圖自己跟自己設定約束)。如下圖就是一個約束的例子竟终,當然要確定一個視圖的位置蝠猬,跟基于frame一樣,也是需要確定視圖的橫縱坐標以及寬度和高度的统捶,只是榆芦,這個橫縱坐標和寬度高度不再是寫死的數值柄粹,而是根據約束計算得來,從而達到自動布局的效果匆绣。

圖3.1 view_formula

約束其實是一個兩個視圖之間的線性關系驻右。如圖3.1所示,就是Blue View和Red View的一條約束崎淳。表示Red View的左邊緣等于Blue View的右邊緣(在從左到右書寫的系統(tǒng)里面堪夭,leading=left,trailing=right) + 8個Point拣凹,注意森爽,在iOS代碼里面都是用的邏輯點,不是真正的物理像素點咐鹤。其中關系可以是=拗秘、>=以及<=這三個的一種,當然我們的例子用的是=祈惶。

還有一個要注意的是雕旨,這里只是給出了一個約束來說明約束的基本范式,顯然一個約束是不能完成Blue View和Red View的自動布局的捧请,下一節(jié)通過實例來看看自動布局具體應該怎么操作狸吞。

3.2 Auto Layout初體驗 & Fitting Size

新建一個Single View Application,然后添加一個View到視圖中潜必,
我們什么約束都不加段标,發(fā)現Xcode是沒有任何錯誤和警告的。但是如果我們自己手動加了一條約束(見圖3.2)可款,Xcode卻會有警告育韩。一開始學習都會有這個困惑,為什么會出現這個情況呢闺鲸?

圖3.1 layout缺少約束

原因其實就是筋讨,如果我們什么約束都不加,那么Xcode其實已經幫你自動加了約束信息了摸恍,這個約束稱之為prototyping constraints悉罕,也就是說,這個添加的Green View的橫縱坐標立镶,寬度高度都已經設定為一個值了(這個值可以在屬性標簽里面看到)壁袄,所以,Green View的位置已經固定媚媒,自然Xcode也就不會有錯誤或警告了嗜逻。而如果我們手動加了一條約束,那么Xcode認為你要自己添加約束了缭召,那么在Auto Layout引擎檢查約束完備性的時候自動添加的約束會被忽略变泄,所以令哟,這個時候因為我們只加了一個Y軸的約束條件,缺少X軸的約束條件妨蛹,因此會報約束錯誤的提示(當然這個并不影響工程的運行屏富,你要編譯運行還是可以的,而且自動添加的約束如果沒有被顯示添加的約束覆蓋蛙卤,也還是會生效的狠半,只是控件的位置可能會存在歧義,影響最終布局效果)颤难。那么我們再加上其他的三個約束神年,好了,錯誤沒有了行嗤。最終添加的約束如下(約束還有優(yōu)先級這個非常重要的屬性已日,后面再談):

圖3.2 添加完整的約束

這四個約束可以用下面的四個等式來表示:

Green View.Trailing = Superview.Trailing Margin
Green View.Leading = Superview.Leading Margin
Green View.Bottom = Bottom Layout Guide.Top + 20
Green View.Top = Top Layout Guide.Bottom + 20

注意到這里引入了幾個變量,一個是Top/Bottom Layout Guide(頂部/底部導航)栅屏,一個是Superview.leading/Trailing Margin(左/右邊緣間距)飘千。Top Layout Guide其實是指的根視圖的頂部,模擬器在豎屏下有狀態(tài)欄栈雳,狀態(tài)欄默認高度為20(注:導航欄與狀態(tài)欄高度不同护奈,導航欄的豎屏默認高度為44,橫屏默認高度為32)哥纫,則Green View的Y坐標就是20 + 20 = 40霉旗。模擬器在橫屏下沒有狀態(tài)欄,則Top Layout Guide.Bottom為0蛀骇,則Green View的Y坐標就是20厌秒。Superview.leading Margin在豎屏時為16,橫屏是為20擅憔。這幾個結論可以通過打印Green View的frame值來驗證:

green view frame:{{16, 40}, {343, 607}} //iPhone6 豎屏
green view frame:{{20, 20}, {627, 335}} //iPhone6 橫屏

我們可以發(fā)現鸵闪,Green View在橫屏和豎屏的大小和位置都是不同的,但是整體布局是我們所希望的效果雕欺。這就是Auto Layout做的事情岛马,通過這些約束棉姐,根據屏幕大小不同屠列,屏幕方向不同來動態(tài)計算控件的大小和位置伞矩。計算方法也很簡單笛洛,比如我們的例子,因為iPhone6的邏輯像素點是375 X 667乃坤,因此可以通過上面的約束計算Green View的大小苛让。由于我們并沒有設置視圖的大小沟蔑,視圖最終呈現的大小是由Auto Layout引擎根據約束計算得到的,這個大小也稱之為視圖的Fitting Size狱杰,這也就是Auto Layout的便捷之處瘦材,我們不需要寫任何代碼去控制

width = 375 - 16*2  = 343, height = 667 - 40 - 20 = 607 //iPhone6 豎屏
width = 667 - 20*2 = 627, height = 375 - 20*2 = 335 //iPhone6 橫屏

3.3 自身內容尺寸 & 抗壓縮抗拉伸效果

先簡化一下這兩個概念:

  • 自身內容尺寸(Intrinsic Content Size仿畸,以下簡稱ICS)食棕。
  • 抗壓縮抗拉伸(Compression-Resistance and Content-Hugging,以下簡稱CRCH)

自身內容尺寸

前面我們添加了一個View到根視圖中错沽,也初次體會到了Auto Layout的強大之處簿晓,接下來我們來添加一個按鈕。如下圖所示千埃,我們只添加了兩個約束憔儿,Xcode居然沒有報錯,這可能讓人納悶了放可,我們并沒有指定按鈕的寬度和高度谒臼,那最終按鈕是如何定位的呢?這就是這一節(jié)要討論的內容吴侦,一些iOS控件如按鈕控件屋休,文本控件等其實是有一個自身內容尺寸的,這類控件會根據自身內容尺寸添加布局約束备韧,如果我們沒有顯示指定控件的寬度和高度劫樟,則其自動添加的約束就會起作用。正如下圖中的按鈕织堂,我們只指定了橫縱坐標的約束叠艳,并沒有指定寬度和高度,但是Xcode并沒有報錯或者警告易阳。

圖3.3 自身內容尺寸完成完整約束

下表列出了一些常用控件的ICS附较,由表中可以發(fā)現,label潦俺, button拒课, text fields等都是有ICS的,而UIView和NSView是沒有ICS的事示。

View Intrinsic content size
UIView and NSView No intrinsic content size.
Sliders Defines only the width (iOS).
Labels, buttons, switches, and text fields Defines both the height and the width.
Text views and image views Intrinsic content size can vary.

控件的ICS基于視圖的當前內容早像。Button或者Label的ICS基于其展示的文字數目和字體大小,空的Image View是沒有ICS的肖爵,只有當你添加了圖片到Image View中卢鹦,這個時候才會有ICS,而且尺寸大小為圖片的尺寸劝堪。

Updated:視圖UIView也是沒有ICS的冀自,有時候想只指定位置而不指定UIView的大小揉稚,可以在Storyboard的Size inspector中設置Intrinsic Size為Placeholder,這樣便不會報錯了熬粗。注意一點的是搀玖,這個設置并不影響運行時UIView的Intrinsic Size。

抗壓縮和抗拉伸效果

抗壓縮(Compression-Resistance) 和抗拉伸(Content-Hugging)效果是跟自身內容尺寸關聯(lián)在一起的驻呐,如圖3.4所示巷怜,抗壓縮定義了視圖抗壓縮的優(yōu)先級,優(yōu)先級越大暴氏,表示越難壓縮延塑;抗拉伸則定義了視圖抗拉伸的優(yōu)先級,優(yōu)先級越大答渔,則越難被拉伸关带。抗壓縮和抗拉伸的優(yōu)先級是針對橫豎兩個方向的沼撕,每個方向都有一個優(yōu)先級宋雏。默認的View和Button的抗壓縮優(yōu)先級為750,抗拉伸優(yōu)先級為250务豺。從優(yōu)先級大小可以看出來磨总,拉伸一個View比壓縮一個View容易。這也符合我們的期望笼沥,比如我們期望拉伸一個按鈕大于其自身內容尺寸蚪燕,而不是縮小按鈕尺寸導致內容顯示不全。

圖3.4 CRCH圖示
// Compression Resistance
View.height >= 0.0 * NotAnAttribute + IntrinsicHeight
View.width >= 0.0 * NotAnAttribute + IntrinsicWidth
 
// Content Hugging
View.height <= 0.0 * NotAnAttribute + IntrinsicHeight
View.width <= 0.0 * NotAnAttribute + IntrinsicWidth

對于兩個控件來說奔浅,為了滿足Auto Layout的約束馆纳,通常會優(yōu)先壓縮那個抗壓縮優(yōu)先級小的控件來適應視圖的布局。

下面看一個例子汹桦,我們在視圖中添加一個Label和一個Text Field鲁驶。然后分別設置了Label的左上的約束和Text Field的右上約束,然后設置Label和Text Field的間距為20舞骆。約束關系我們可以看到左邊的5個等式钥弯,因為Label和Text Field都有自身內容尺寸,所以這5個等式已經可以完成布局了督禽。在這個例子中我們看到Text Field被拉伸了脆霎,而Label還是保持自身內容尺寸的,這是因為Label的默認抗拉伸優(yōu)先級為251大于Text Field的默認抗拉伸優(yōu)先級250赂蠢,因此Label更難被拉伸绪穆,所以看到的是Text Field被拉伸了辨泳。那如果我們把Text Field的抗拉伸優(yōu)先級改為252虱岂,則最終運行的界面如圖3.5.4所示玖院。

圖3.5.1 默認的CRCH效果
圖3.5.2 Label的CRCH優(yōu)先級
圖3.5.3 Text Field的CRCH優(yōu)先級
圖3.5.4 增大了Text Field的CRCH效果

接下來再看一個Image View的例子,可以看看自身內容尺寸和CRCH對Image View的影響第岖。這里我在Image View里面加了個apple.jpg的圖片难菌,圖片原始尺寸為241*300。開始的時候我設置Image View水平垂直居中蔑滓,不設置寬度高度郊酒,則Image View的寬度和高度為圖片原始尺寸241和300。然后再添加一個寬度約束键袱,設置圖片寬度為300燎窘。由于顯示添加的約束的默認優(yōu)先級為1000,而Image View的抗拉伸的優(yōu)先級為251蹄咖,所以會以顯示添加的約束為準褐健,圖片寬度會被拉升到300。而如果我們把顯示添加的寬度約束的優(yōu)先級改成250澜汤,則圖片寬度會被設置為原始寬度241蚜迅。

圖3.5.5 Image View的CRCH效果

4 更多例子

4.1 兩個寬度相等的View

4.1兩個寬度相等的View
約束關系:
1.Yellow View.Leading = Superview.LeadingMargin
2.Green View.Leading = Yellow View.Trailing + Standard
3.Green View.Trailing = Superview.TrailingMargin
4.Yellow View.Top = Top Layout Guide.Bottom + 20.0
5.Green View.Top = Top Layout Guide.Bottom + 20.0
6.Bottom Layout Guide.Top = Yellow View.Bottom + 20.0
7.Bottom Layout Guide.Top = Green View.Bottom + 20.0
8.Yellow View.Width = Green View.Width

4.2 兩個寬度不等的View

圖4.2 兩個寬度不等的View
約束關系:
1.Purple View.Leading = Superview.LeadingMargin
2.Orange View.Leading = Purple View.Trailing + Standard
3.Orange View.Trailing = Superview.TrailingMargin
4.Purple View.Top = Top Layout Guide.Bottom + 20.0
5.Orange View.Top = Top Layout Guide.Bottom + 20.0
6.Bottom Layout Guide.Top = Purple View.Bottom + 20.0
7.Bottom Layout Guide.Top = Orange View.Bottom + 20.0
8.Orange View.Width = 2.0 x Purple View.Width

4.3 自身內容尺寸

圖4.3 自身內容尺寸布局
約束:
1.Name Label.Leading = Superview.LeadingMargin
2.Name Text Field.Trailing = Superview.TrailingMargin
3.Name Text Field.Leading = Name Label.Trailing + Standard
4.Name Text Field.Top = Top Layout Guide.Bottom + 20.0
5.Name label.Baseline = Name Text Field.Baseline

這個例子跟前面提到的類似,注意并不需要設置Label和Text Field的寬度和高度俊抵。而且默認設置中谁不,Label的抗拉伸的優(yōu)先級251比Text Field的250更高,所以最終看到的效果是Text Field被拉伸了徽诲。

4.4 自適應View

圖4.4 自適應View
約束:
1.Blue View.Leading = Superview.LeadingMargin
2.Blue View.Trailing = Superview.TrailingMargin
3.Blue View.Top = Top Layout Guide.Bottom + Standard (Priority 750)
4.Blue View.Top >= Superview.Top + 20.0
5.Bottom Layout Guide.Top = Blue View.Bottom + Standard (Priority 750)
6.Superview.Bottom >= Blue View.Bottom + 20.0

前面的例子都是=的約束刹帕,這個例子加了>=的約束。
注意到我們設置的>=的約束4優(yōu)先級比約束3要高谎替,約束6的優(yōu)先級比約束5的高轩拨,這樣如果顯示狀態(tài)欄(模擬器里面豎屏的時候),我們知道狀態(tài)欄的高度為20院喜,那么這時約束3滿足的時候亡蓉,也就是Blue View的y坐標為28(狀態(tài)欄高度20+標準距離8),這時約束4也滿足喷舀,因此會選擇約束3這個優(yōu)先級較低的約束砍濒。如果不顯示狀態(tài)欄(模擬器里面橫屏的時候),則此時只能滿足約束4硫麻,無法滿足約束3爸邢。不過Auto Layout引擎會選擇一個最接近的約束,也就是設置Blue View的y坐標為20拿愧。

更多例子:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSimpleConstraints.html#//apple_ref/doc/uid/TP40010853-CH12-SW1
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/ViewswithIntrinsicContentSize.html#//apple_ref/doc/uid/TP40010853-CH13-SW1

Stack View布局例子:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html#//apple_ref/doc/uid/TP40010853-CH11-SW1

Size Class例子:
https://www.raywenderlich.com/113768/adaptive-layout-tutorial-in-ios-9-getting-started

使用代碼和VFL來添加約束可以參見:
http://blog.csdn.net/pucker/article/details/45070955
http://blog.csdn.net/pucker/article/details/45093483

5 參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末杠河,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌券敌,老刑警劉巖唾戚,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異待诅,居然都是意外死亡叹坦,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門卑雁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來募书,“玉大人,你說我怎么就攤上這事测蹲∮瘢” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵扣甲,是天一觀的道長道盏。 經常有香客問我,道長文捶,這世上最難降的妖魔是什么荷逞? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮粹排,結果婚禮上种远,老公的妹妹穿的比我還像新娘。我一直安慰自己顽耳,他們只是感情好坠敷,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著射富,像睡著了一般膝迎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胰耗,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天限次,我揣著相機與錄音,去河邊找鬼柴灯。 笑死卖漫,一個胖子當著我的面吹牛,可吹牛的內容都是我干的赠群。 我是一名探鬼主播羊始,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼查描!你這毒婦竟也來了突委?” 一聲冷哼從身側響起柏卤,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎匀油,沒想到半個月后缘缚,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡钧唐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了匠襟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钝侠。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酸舍,靈堂內的尸體忽然破棺而出帅韧,到底是詐尸還是另有隱情,我是刑警寧澤啃勉,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布忽舟,位于F島的核電站,受9級特大地震影響淮阐,放射性物質發(fā)生泄漏叮阅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一泣特、第九天 我趴在偏房一處隱蔽的房頂上張望浩姥。 院中可真熱鬧,春花似錦状您、人聲如沸勒叠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眯分。三九已至,卻和暖如春柒桑,著一層夾襖步出監(jiān)牢的瞬間弊决,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工魁淳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丢氢,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓先改,卻偏偏與公主長得像疚察,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子仇奶,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容