這是關(guān)于自動(dòng)布局的第一篇文章。
>> Stack View的使用
自動(dòng)布局(Auto Layout)能夠根據(jù)對(duì)視圖的約束(Constraint),動(dòng)態(tài)地計(jì)算視圖層次結(jié)構(gòu)中所有視圖的大小和位置抚吠。
基于約束的Auto Layout使我們能夠搭建動(dòng)態(tài)響應(yīng)內(nèi)部、外部變化的用戶(hù)界面。外部變化包括用戶(hù)改變窗口大刑ǖ(OS X)、旋轉(zhuǎn)設(shè)備(iOS)吁断、在iPad上進(jìn)入或離開(kāi)分割視圖(iOS)趁蕊、不同屏幕尺寸坞生,內(nèi)部變化包括app顯示內(nèi)容長(zhǎng)度變化、字體大小變化掷伙、對(duì)國(guó)際化的支持等是己。
大部分的外部變化會(huì)在運(yùn)行時(shí)發(fā)生,這就要求app要?jiǎng)討B(tài)的調(diào)整視圖布局任柜。盡管屏幕尺寸不會(huì)改變卒废,但創(chuàng)建一個(gè)自動(dòng)布局的界面就可以適用于iPhone SE、iPhone 7 Plus宙地、甚至iPad不同尺寸屏幕的設(shè)備摔认。
當(dāng)用戶(hù)界面中視圖或控件大小變化時(shí)會(huì)導(dǎo)致內(nèi)部變化。如對(duì)國(guó)際化的支持绸栅,把用戶(hù)界面上的文字改變?yōu)槠渌Z(yǔ)言時(shí)级野,新的語(yǔ)言可能占用不同大小空間;不同的語(yǔ)言有不同布局方向粹胯,如英語(yǔ)蓖柔、中文都是自左向右,而阿拉伯語(yǔ)自右向左风纠,這時(shí)中况鸣、英文界面右下角的按鈕在阿拉伯語(yǔ)中應(yīng)在左下角。如app支持調(diào)整字體大小竹观,調(diào)整后用戶(hù)界面中任何文本的高度和寬度都會(huì)發(fā)生變化镐捧,此時(shí)需要調(diào)整布局。
1. Auto Layout與Frame-Based Layout比較
布局用戶(hù)界面方式有三種臭增,第一種是代碼方式懂酱,第二種是使用Autoresizing masks,第三種是使用Auto Layout誊抛。
通過(guò)代碼方式來(lái)布局用戶(hù)界面時(shí)需要設(shè)定視圖在其父視圖坐標(biāo)系中的位置和大小列牺。
為布局界面,你必須計(jì)算視圖層級(jí)中每一個(gè)視圖的位置和大小拗窃。如果其中一個(gè)發(fā)生了改變瞎领,你就需要再次計(jì)算所有受影響的視圖。在許多方面随夸,以代碼方式構(gòu)建的界面會(huì)更靈活九默、更強(qiáng)大。當(dāng)界面有變化時(shí)宾毒,你可以操作其它視圖的變化驼修。也正因?yàn)槟阈枰刂破渌晥D的變化,構(gòu)建一個(gè)簡(jiǎn)單的界面可能需要大量工作,構(gòu)建一個(gè)自適應(yīng)的界面就變的更加困難邪锌。
第二種方式是使用Autoresizing masks來(lái)構(gòu)建界面勉躺,Autoresizing masks指定視圖如何隨父視圖的變化而改變,這簡(jiǎn)化了適應(yīng)外部變化布局的復(fù)雜度觅丰。Autoresizing masks用于較小數(shù)量的可能布局,對(duì)于復(fù)雜的用戶(hù)界面妨退,需要結(jié)合使用代碼布局妇萄。另外,Autoresizing masks只適用于外部變化咬荷,不響應(yīng)內(nèi)部變化冠句。
第三種方式為使用Auto Layout添加一系列約束來(lái)構(gòu)建界面,這些約束代表兩個(gè)視圖間的關(guān)系幸乒,最后Auto Layout根據(jù)這些約束計(jì)算出視圖的位置和大小懦底。Auto Layout動(dòng)態(tài)的響應(yīng)內(nèi)部、外部變化罕扎。
視圖層次結(jié)構(gòu)布局被定義為一系列線(xiàn)性方程聚唐。每個(gè)約束表示一個(gè)方程。目標(biāo)是聲明一系列方程腔召,最后只有一個(gè)可能的布局方案杆查。下圖是一個(gè)示例方程。
這個(gè)約束表示Red視圖的Leading必須在Blue視圖Trailing后8
points亲桦,該方程由以下幾部分組成:
- Item 1:方程式中的第一項(xiàng)客峭,這里指Red視圖抡柿。第一項(xiàng)必須是視圖或布局參考線(xiàn)(Layout Guide)。
- Attribute 1:在Item 1添加約束的位置搏明。在這里是Red視圖的Leading闪檬。
- Relationship:左右兩側(cè)的關(guān)系星著,可以是equal、greater than or equal和less than or equal之一粗悯。在這里左右兩側(cè)相等虚循。
-
Multiplier:第二項(xiàng)的值乘以該浮點(diǎn)值。在這里,乘數(shù)是
1.0
横缔。 - Item 2:方程式中的第二項(xiàng)铺遂,在這里是Blue視圖。與第一項(xiàng)不同茎刚,這一項(xiàng)可以為空襟锐。
- Attribute 2:在Item 2添加約束的位置。在這里是Blue視圖的Trailing膛锭。如果Item 2為空粮坞,這里也要為空。
-
Constant:浮點(diǎn)類(lèi)型的常量初狰。在這里是
8.0
莫杈,該值被添加到Attribute 2。
通過(guò)創(chuàng)建這樣的方程可以創(chuàng)建多種約束奢入,可以定義兩個(gè)視圖間的距離筝闹,對(duì)齊視圖、定義兩個(gè)視圖大小關(guān)系腥光、定義視圖寬高比(Aspect Ratio)关顷。
需要說(shuō)明的是上面方程式中的=
是相等,不是賦值柴我。當(dāng)Auto Layout布局界面時(shí)解寝,會(huì)計(jì)算Attribute 1和Attribute 2的值直到等式成立聋伦,而不會(huì)直接把右側(cè)的值賦值給左側(cè)觉增。因此我們可以互換等式左右兩側(cè)的值,但遵守下面規(guī)則的約束更易于維護(hù)嘹履。不符合下面規(guī)則的約束可以通過(guò)互換等式兩側(cè)解決。
- Multiplier優(yōu)先使用整數(shù)而非分?jǐn)?shù)窒篱。
- 常量?jī)?yōu)先使用正數(shù)而非負(fù)數(shù)配并。
- 如果可能溉旋,視圖更應(yīng)該自上而下召夹,自左向右布局。
約束既可以用來(lái)描述界面中兩個(gè)視圖間的關(guān)系婶溯,也可以定義一個(gè)視圖中不同屬性間的關(guān)系。例如設(shè)置視圖高度和寬度的高寬比叙身。
在自動(dòng)布局中信轿,可供約束的屬性有左右上下四邊(leading,trailing,top和bottom)、寬即彪、高隶校、水平居中和垂直居中等。
2. 創(chuàng)建demo
這里創(chuàng)建一個(gè)Single View Application模板的應(yīng)用宁仔,Product Name為AutoLayout
权埠,Language為Objective-C龙屉,Devices為Universal转捕,選擇文件位置,創(chuàng)建工程枢步。
下面通過(guò)幾個(gè)示例來(lái)體驗(yàn)Auto Layout。
3. 示例1
打開(kāi)Main.storyboard隘擎,添加5個(gè)UILabel
货葬。如下圖所示,一個(gè)在中心再沧,其余每個(gè)角各一個(gè)淤堵。拖放UILabel
時(shí)會(huì)出現(xiàn)藍(lán)色虛線(xiàn)拐邪,放在這些藍(lán)色虛線(xiàn)位置汹胃。
點(diǎn)擊Xcode右上角的Assistant editor,這時(shí)一般顯示的是你所選中視圖控制器的代碼,點(diǎn)擊左上角的Automatic赁濒,從彈出菜單中選擇Preview > Main.storyboard(Preview)。
可以通過(guò)點(diǎn)擊上圖左下角的+
按鈕添加多個(gè)設(shè)備的預(yù)覽舞丛,這里只添加iPhone SE谷誓。
通過(guò)上圖可以看到,在4英寸的iPhone SE中庐镐,只有左上角一個(gè)Label
顯示正常必逆。這是因?yàn)槿绻晥D沒(méi)有任何約束凰棉,在Build Time系統(tǒng)會(huì)自動(dòng)為視圖添加至左上角、目前大小的約束福压。當(dāng)你添加第一個(gè)約束時(shí),系統(tǒng)會(huì)自動(dòng)清除所有系統(tǒng)添加的約束杆煞,此時(shí)系統(tǒng)會(huì)出現(xiàn)紅色警告提示約束不足。當(dāng)你添加了一個(gè)約束時(shí)构诚,你就需要添加可滿(mǎn)足布局的所有約束。
還記得之前我們說(shuō)到的藍(lán)色虛線(xiàn)嗎员魏?它們不是這里說(shuō)到的約束丑蛤,它們是Xcode提供的布局參考線(xiàn)。
在Interface Builder里有三種添加約束的方式撕阎。第一種是在視圖間control
+鼠標(biāo)左鍵拖拽受裹,也可以在Document Outline拖拽,在視圖間拖拽的好處是彈出菜單會(huì)根據(jù)拖拽方向改變虏束。第二種是使用Align和Add New Constraints工具棉饶,第三種是讓Interface Builder設(shè)置約束之后我們修改約束。自動(dòng)布局菜單共以下五項(xiàng):
自左向右依次為:
- Update Frame:用于在約束更新后幸缕,更新frame。
- Embed in Stack:用于嵌入Stack View,會(huì)根據(jù)目前選中視圖自動(dòng)嵌入Horizontal Stack View或Vertical Stack View产艾。
- Align:用于設(shè)置視圖水平中心、垂直中心软舌、基線(xiàn)等糟描。
- Add New Constraints:用于設(shè)置視圖與最近視圖米诉、或控制器Margin距離惊橱,視圖寬卓囚、高篡殷、寬高比等邑跪。
- Resolve Auto Layout issues:添加建議約束、刪除約束,其中上半部分對(duì)選中視圖有效钓觉,下半部分對(duì)所有視圖有效嗓节。
選中左上角Label
豆瘫,點(diǎn)擊Add New Constraints定页,在彈出的菜單頂部一欄左側(cè)和上部分別填寫(xiě)0
和20
蜂桶,點(diǎn)擊底部Add 2 Constraints添加約束。
用同樣方式為另外三個(gè)角落處的Label
添加約束,約束均是到距離自己最近的邊緣囊扳,其中Leading和Trailing為0
吩翻,Top和Bottom為20
。
應(yīng)盡量使用
Leading
和Trailing
锥咸,避免使用Left
和Right
狭瞎,這樣布局會(huì)適應(yīng)視圖的閱讀方向。閱讀方向隨用戶(hù)設(shè)定的當(dāng)前語(yǔ)言而變搏予,你也可以自己設(shè)定熊锭。
最后選中視圖中心的Label
,點(diǎn)擊Align,勾選彈出窗口中Horizontally in Container和Vertically in Container選項(xiàng)框碗殷,后面數(shù)字均為0
精绎,點(diǎn)擊Add 2 Constraints添加約束。
現(xiàn)在進(jìn)入Preview查看锌妻,一切正常代乃。可以點(diǎn)擊紅色標(biāo)記處的旋轉(zhuǎn)按鈕从祝,查看橫屏(Landscape)狀態(tài)下Label
位置襟己、大小。
4. 示例2
繼續(xù)在剛才的demo中進(jìn)行操作牍陌,打開(kāi)Main.storyboard擎浴,從對(duì)象庫(kù)拖拽出一個(gè)View Controller,在上面添加兩個(gè)UIView
毒涧,左右各一個(gè)贮预,左側(cè)顏色為Blue,右側(cè)顏色為Orange契讲。
兩個(gè)視圖布局約束是:Blue視圖與Top和Bottom距離是20
仿吞,與Leading距離0
,與Orange視圖距離是standard捡偏。Orange視圖與Top和Bottom距離是20
唤冈,與Trailing距離0
。同時(shí)Orange視圖寬度是Blue視圖寬度的二倍银伟。
因?yàn)檫@里設(shè)置Leading你虹、Trailing、Top和Bottom約束與前面相同彤避,不再敘述傅物。如果遇到問(wèn)題,可以在我的Github中查看琉预。另外董饰,文章底部也會(huì)提供demo地址。
下面添加兩個(gè)視圖間間距的約束圆米。選中Blue視圖卒暂,點(diǎn)擊Add New Constraints按鈕,在彈出窗頂部點(diǎn)擊Trailing文本框內(nèi)的下三角娄帖,選擇Use Standard Value也祠,最后點(diǎn)擊Add 1 Constraint。另外块茁,Standard Value默認(rèn)為8
齿坷。
現(xiàn)在添加兩個(gè)視圖寬度的約束。同時(shí)選中兩個(gè)視圖数焊,點(diǎn)擊Add New Constraints按鈕永淌,在彈出窗中勾選上圖中的Equal Widths選項(xiàng)。最后點(diǎn)擊Add 1 Constraint佩耳。如果視圖出現(xiàn)黃色警告線(xiàn)遂蛀,表示當(dāng)前位置與視圖約束位置不同,點(diǎn)擊Update Frames更新視圖位置干厚。
現(xiàn)在添加Orange視圖寬度為Blue視圖寬度二倍的約束李滴。選中任意一個(gè)視圖,點(diǎn)擊底部=
約束線(xiàn)蛮瞄,右側(cè)會(huì)自動(dòng)打開(kāi)Attribute Inspector所坯,鼠標(biāo)放在First Item會(huì)看到選中的是Orange視圖,所以這里的等式為
Orange View.Width = 2.0 x Blue View.Width
所以挂捅,修改Multiplier為2
芹助。
在Preview查看如下,在模擬器查看效果一樣闲先。
在Add New Constraints一項(xiàng)中頂部有Constrain to margins選項(xiàng)框状土。如果勾選上,約束添加到父視圖的margin屬性伺糠;如果取消勾選蒙谓,約束添加到父視圖的edge屬性。如果在一個(gè)控制器上添加一個(gè)UIView
训桶,設(shè)置背景色為Red累驮,到四邊的約束均為0
,下面第一個(gè)圖為勾選Constrain to margins渊迁,第二個(gè)為不勾選慰照。想要了解詳細(xì)區(qū)別,點(diǎn)擊這里琉朽。
5. 示例3
繼續(xù)對(duì)示例2中的視圖進(jìn)行操作毒租,這次限定Blue視圖寬度大于或等于150
。只有當(dāng)屏幕足夠?qū)挄r(shí)Orange視圖寬度為Blue視圖寬度二倍才實(shí)現(xiàn)箱叁,也就是可選實(shí)現(xiàn)墅垮。
選中Blue視圖,點(diǎn)擊Add New Constraints添加寬度為150
的約束耕漱。保持Blue視圖選中狀態(tài)算色,打開(kāi)Size Inspector,雙擊Constraints一欄中寬度為150
的約束螟够。也可以單擊Edit按鈕灾梦。
雙擊后彈出如下:
把上圖中的Equal修改為Greater than or Equal峡钓,這樣寬度就會(huì)大于等于150
。
上圖中的Priority為約束優(yōu)先級(jí)若河。所有約束默認(rèn)必須實(shí)現(xiàn)(Required)能岩,也可以創(chuàng)建可選實(shí)現(xiàn)(Optional)的約束。
所有約束有一個(gè)優(yōu)先級(jí)屬性萧福,它的值在1
和1000
間拉鹃。優(yōu)先級(jí)為1000
的約束必須實(shí)現(xiàn),其它優(yōu)先級(jí)的約束為可選實(shí)現(xiàn)鲫忍。當(dāng)自動(dòng)布局計(jì)算布局方式時(shí)膏燕,會(huì)優(yōu)先滿(mǎn)足高優(yōu)先級(jí)的約束。如果不能滿(mǎn)足低優(yōu)先級(jí)的約束悟民,該約束會(huì)被跳過(guò)坝辫,但被跳過(guò)的約束不是完全無(wú)效,Auto Layout計(jì)算布局方案時(shí)會(huì)選擇最接近被跳過(guò)約束的方案射亏。
雖然優(yōu)先級(jí)的值是1
到1000
阀溶,但系統(tǒng)把優(yōu)先級(jí)劃分成了低(250)、中(500)鸦泳、高(750)和必需(1000)四類(lèi)银锻,你只需要把約束優(yōu)先級(jí)設(shè)定為高于或低于這些值一到兩個(gè)點(diǎn)即可。
現(xiàn)在storyboard出現(xiàn)紅色警告做鹰,提示Blue視圖寬度大于等于150
與Orange視圖寬度為Blue視圖寬度二倍沖突击纬,把后者優(yōu)先級(jí)設(shè)定為750
。Preview顯示如下:
可以看到钾麸,在iPhone的豎屏(Portrait)中兩個(gè)視圖寬度幾乎一樣寬更振,在橫屏(Landscape)中,Orange視圖寬度是Blue視圖寬度二倍饭尝。即優(yōu)先滿(mǎn)足了Blue視圖寬度大于等于150
這一約束肯腕。
6. 示例4
目前為止,所有示例均用約束指定視圖的位置和大小钥平,但有些視圖的大小根據(jù)內(nèi)容變化实撒,這稱(chēng)為固有內(nèi)容大小(Intrinsic Content Size)。例如涉瘾,UIButton
的intrinsic content size是標(biāo)題大小外加很小margin知态。
不是所有的視圖都有intrinsic content size。如:UIView
和NSView
不具有固有內(nèi)容大小立叛,UILabel
负敏、UIButton
、UISwitch
和UITextField
高和寬都具有固有內(nèi)容大小秘蛇,UIImageView
沒(méi)有添加圖片時(shí)不具有intrinsic content size其做,添加圖片后固有內(nèi)容大小與圖片大小一致顶考。
Auto Layout在每個(gè)維度使用一對(duì)約束來(lái)表示視圖的intrinsic content size。內(nèi)容擁抱(Content hugging)將視圖向內(nèi)拉妖泄,使其緊貼內(nèi)容村怪。內(nèi)容壓縮阻力(Content Compression Resistance)將視圖向外推,使其內(nèi)容能夠完整顯示浮庐、不被壓縮。
固有內(nèi)容大小的約束有自身的優(yōu)先級(jí)柬焕。一般审残,content hugging優(yōu)先級(jí)為250
,content compression resistance優(yōu)先級(jí)750
斑举。因此搅轿,一般拉伸視圖比壓縮視圖容易。例如:你可以把button拉伸一些富玷,不影響使用璧坟。但如果壓縮一些,內(nèi)容可能無(wú)法完整顯示赎懦。
繼續(xù)在剛才的demo里操作雀鹃,從對(duì)象庫(kù)拖拽出一個(gè)View Controller,在頂部添加一個(gè)UILabel
和一個(gè)UITextField
励两。
添加約束黎茎,讓Label與Leading距離為0
,與Text Field的Leading距離為Standard值当悔,與Text Field基線(xiàn)在同一水平位置傅瞻;讓Text Field與Top距離20
,與Trailing距離為0
盲憎。兩個(gè)視圖寬和高均為固有內(nèi)容大小嗅骄。
這里添加約束與前面示例相同,不再敘述添加過(guò)程饼疙。只說(shuō)一下添加基線(xiàn)約束溺森。選中兩個(gè)視圖,點(diǎn)擊Align窑眯,勾選Baselines儿惫,點(diǎn)擊添加Add 1 Constraint。
UILabel
和UITextField
在一起時(shí)伸但,一般UILabel
保持自身寬度肾请,拉伸UITextField
,以此填充可用空間更胖。所以铛铁,應(yīng)當(dāng)設(shè)置UILabel
的content hugging優(yōu)先級(jí)為251
隔显,UITextField
的為250
。
在Size Inspector檢查content hugging饵逐,你會(huì)發(fā)現(xiàn)Interface Builder已經(jīng)設(shè)置好了優(yōu)先級(jí)括眠。另外,如果你是用代碼添加自動(dòng)布局倍权,需要手動(dòng)設(shè)定這里的優(yōu)先級(jí)掷豺。
點(diǎn)擊Update Frames更新frame。在Preview查看布局薄声。
只有當(dāng)高為固有內(nèi)容大小時(shí)基線(xiàn)才可以正確對(duì)齊当船。如果高被壓縮或拉伸,基線(xiàn)將不能正確對(duì)齊默辨。
一般在處理示例4
中的約束時(shí)應(yīng)該使用Stack View德频。Stack View提供了一種輕松的方式利用自動(dòng)布局的功能,而不會(huì)引入復(fù)雜的約束缩幸,在下一篇文章我們將詳細(xì)介紹Stack View的使用壹置。
Demo名稱(chēng):AutoLayout
源碼地址:https://github.com/pro648/BasicDemos-iOS
參考資料: