當內部或外部條件發生變化的時候,自動重新計算視圖的位置和大小。
對於ios應用,可以觸發自動布局的變化可以分為外部變化和內部變化:
外部的變化可以是設備的方向改變,ipad的分屏,不同尺寸的屏幕等。
內部的變化可以是內容的變化(加載不同的圖片等),應用支持動態類型(允許用戶修改視圖),國際化(添加對多國使用者的支持)等。
ios的自動布局與基於frame布局的關系,就像安卓的相對布局與絕對布局的關系。基於frame的布局,也就是通過子視圖相對於父視圖坐標的絕對位置和絕對大小,這種布局的好處在於坐標位置確定,但是在面對多種尺寸屏幕,動態內容等問題時顯得非常的麻煩。而自動布局不同,它描述的是視圖與視圖之間的位置關系,無論屏幕大小或者內容如何改變,視圖之間的相對位置不變,視圖排版就不會亂掉。
自動布局需要通過約束constraint來實現。下面,先說明使用約束來實現自動布局的原理,後說明如何創建、修改、查看約束。
布局,實際上是關於視圖位置的一系列方程。每個方程就是一個約束,一個約束通常描述的就是兩個屬性之間的數值關系,比如長是寬的多少倍、view2的起始位置是view1的計數位置加10等。
圖中的方程就是一個典型的約束。要約束的對象就是RedView(item1)的leading屬性(Attribute1),約束的具體內容就是BlueView(item2)的trailing屬性(Attribute2)乘上1.0再加上8.0。這個1.0叫做乘積因子multiplier,8.0是個常量constant。這幾個量的意義要明確,因為之後在創建約束的時候離不開這幾個量。
視圖的四個邊界:leading(左邊界)、trailing(右邊界)、top(上邊界)、bottom(下邊界)
視圖的大小:height(高)、width(寬)
視圖的中心:center(中心坐標)
首先,分清自動布局的屬性可以分為位置相關屬性和大小相關屬性。這兩個屬性之間不應該有任何關聯,具體要遵守以下原則:
不要用位置屬性去約束大小屬性。
不要給位置屬性賦常量值。
位置屬性的約束中乘積因子只能是1.0。
不要用水平方向的位置屬性去約束垂直方向的位置屬性。
不要用leading或trailing屬性來約束left或right屬性。
對於以方程形式存在的約束,並不是把等號右邊的值賦給等號左邊的值,而是通過解方程,使得等號兩邊相等。
因此,當方程有多個解,也就是說可能有多種布局方案可以滿足這些約束,這時候,就要從中確定一種方案,並且一直運用下去。比如說:整數的乘積因子比小數乘積因子優先,正數乘積因子比負數優先,views的排列按照從上至下,從左至右排列等等。這些規則需要自己去定,並且在一個項目中一直遵守。
使用自動布局的目標是可以產生一套確定的布局,也就是說同時滿足所有約束的布局方案盡可能地少。就像兩點確定一條直線一樣,要創建一個確定的布局,也有它的規則:兩個屬性確定一個view的一個維度的位置。
如果確定一個view在水平方向上的位置(三種方法,對應下圖左、中、右)
方法一:同時約束左邊界相對於父視圖的距離,以及視圖的寬
方法二:同時約束左右邊界相對於父視圖的距離
方法三:同時約束左邊界相對於父視圖的距離,以及視圖的中心位置
對以上三種方法的分析:
方法一:缺點是視圖的寬不可以隨父視圖的大小變化而變化
方法二:左右邊界相對於父視圖的距離固定(兩個距離可以不相等)
方法三:在需要安置許多中心對齊的視圖的時候,這個方法最便利
在約束的方程中,除了可以等號=,也可以有大於號>、小於號<、大於等於>=和小於等於<=。
不等式約束了屬性的變動范圍。可以用等式約束和不等式約束一起來確定布局。
默認情況下,自動布局會去計算出一個可以滿足所有約束條件的布局方案,如果計算不出來,就會把不能滿足的約束條件在控制台中輸出,並且選擇其中一個,不去遵守,然後再重新計算一次。
可以通過確定約束的優先級來創建可選的約束。優先級范圍在1-1000。優先級為1000的約束就是必須要滿足的,其他的都是可選的。自動布局會按優先級順序來計算布局方案,如果滿足不了可選的約束,就會跳過。如果跳過了某條約束以後,布局變得不確定(即有多種方案),那麼布局會選擇一個最接近那條約束的值來確定布局。
有些控件會有默認的大小,比如常見的Sliders有默認的寬,label、button、switch、textField等有默認的寬和高。
通常這些控件的默認大小是基於它的內容的。就像imageView設置了圖片以後,默認就是圖片大小。imageView和textView的默認大小,會受到內容,還有是否允許滾動,還有其他的約束條件的影響。
iOS官方建議盡可能地使用視圖的默認大小,因為它允許視圖自動調整大小以適應其內容的變化,也可以減少要指定的約束數目。
使用默認大小的視圖,通常要聯合兩個與大小相關的約束,一個是“防止被拉伸”,一個是“防止被壓縮”,文檔上把它叫做CHCR(Content-Hugging and Compression-Resistance)。使用這兩個約束的重點是,注意調整視圖與視圖之間CHCR的優先級的相對大小。比如說,在同一水平高度上有兩個視圖,都是使用默認大小的,當需要將這兩個視圖(都比較小)拉伸以填滿父視圖的寬度的時候,就會去比較這兩個視圖在水平方向上的“防止被拉伸”約束的優先級,誰的優先級小,相應的視圖就會被拉伸,而另一個視圖保持默認大小。如果這兩個優先級相等,那自動布局機制就不知道應該拉伸哪個視圖了。
通常,使用默認大小的視圖布局出現意料之外的結果,很多時候,可能是被拉伸了。為了避免這種情況,防止控件被拉伸,可以把“防止被拉伸”約束(Content-Hugging)的優先級定大一點。
關於基線(baseline)的約束,只會對維持著默認高度的視圖起作用。
有些屬性是可以在寫約束的時候作為參照物(約束方程中的item2)來用的:
我這裡這裡所說的上、下導航欄,不是狹義的navigationBar和tabBar,而是泛指當前根視圖view的頂部和底部被遮擋了的部分(通常是導航欄之類)。具體來說,就是,當statusBar和半透明的navigationBar都可見的時候,topLayoutGuide就是狀態欄加導航欄的部分,navigationBar不可見的時候,topLayoutGuide就是狀態欄部分,如果連狀態欄也隱藏了,那topLayoutGuide就沒有了。而對於bottomLayoutGuide來說,當tabBarController.tabBar或toolBar可見的時候,bottomLayoutGuide就是tabBar或toolBar部分,而當tabBar不可見的時候,bottomLayoutGuide就沒有了。
這兩個屬性本身也遵循了一個協議,UILayoutSupport協議。這個協議定義了4個屬性:length、bottomAnchor、heightAnchor、topAnchor。
length是什麼?對於topLayoutGuide來說,就是topLayoutGuide的下邊界;對於bottomLayoutGuide來說,就是bottomLayoutGuide的上邊界 。
如果在代碼中直接訪問viewController.topLayoutGuide會返回viewController.topLayoutGuide.length,訪問bottomLayoutGuide也是返回bottomLayoutGuide.length。因此在代碼中,也可以直接把topLayoutGuide和bottomLayoutGuide理解成是viewController.view的可視區域的上下邊界。
不過在可視化的autoLayout當中,還是把這兩者理解成viewController.view上下被遮擋的部分。
bottomAnchor、heightAnchor、topAnchor是幫助在代碼實現中實現autoLayout用的,這裡先不細說。
內邊距就是view的邊界到子視圖的最小距離,這個不多說。
UIViewController有兩個與內邊距有關的屬性,一個叫layoutMargins,一個叫layoutMarginsGuide。layoutMargins就是內邊距的大小。layoutMarginsGuide指的就是內邊界。
在寫約束的時候,通常都是相對於內邊界。默認的內邊距大小是8個點。
UIViewController有一個與文本的邊界有關的屬性readableContentGuide。指的就是文本可以顯示的最大的區域的邊界。通常情況下,這個邊界和內邊界不會有太大的區別,只有在pad橫屏的時候會有比較明顯的區別。必要時,可以選擇讓布局相對於這個文本邊界,而不相對於內邊界。
約束的管理可以通過可視化的Interface Builder來實現,也可以通過代碼來是實現。第二節,也就是本節,先講InterfaceBuilder管理約束的方法。下一節講代碼的實現。
實現方法是通過直接拖動view,並且在xcode右側的interface Builder中設置相關約束來實現的。其實約束是可以用代碼來實現的。本節中先不說。下一節說。
對於之前都只是用代碼來創建view的同學有必要先知道怎麼創建xib和關聯xib。如果已經會的這一小段略過。
(1)方法一:創建ViewController的同時創建xib。和平時新建ViewController一樣,就是多勾選了Also create XIB file。這樣就會自動新建一個xib,並且和所創建的ViewController已經關聯了。
(2)方法二:自己創建xib,並與現有的ViewController關聯起來。
通過new File 創建xib。要把xib與現有的ViewController關聯起來,只要一步。
打開xib,在左側找到這樣的圖標vc/Cw+a1xEN1c3RvbSBDbGFzc6Os1NpDbGFzc8C41tDRodTxxOPSqrnYwaq1xFZpZXdDb250cm9sbGVyoaM8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160420/20160420093535541.png" title="\" />
注意:需要設定xib的根視圖,方法是按住Ctrl同時,點擊上面file’s Owner,拖向view。即可。
注意:之後在創建ViewController時還需要注意,不要直接[[ViewController alloc ]init],而要使用initWithNibName:bundle:方法進行初始化,指定該xib文件作為自己的根視圖。
回到正題,使用約束來實現自動布局:
按住control+鼠標點擊item1並拖向item2,就可以建立item1的某屬性對item2的某屬性的約束(如圖)。
第一個工具是可以快速建立stackView。
第二個工具可以快速建立有關視圖對齊的約束。
第三個工具可以快速建立有關視圖的寬、高、長寬比例、視圖與湘…?/database/DB2/" target="_blank" class="keylink">DB2srTzby85L7gtcTUvMr4oaM8YnIgLz4NCrXay8S49rmkvt+/ydLUyrXP1tS8yvi1xNfUtq/J+rPJoaLUvMr4tcTH5bP9oaLK0828sry+1rXEuPzQwrXIoaM8YnIgLz4NCsq508PV4svEuPa5pL7ftcTXotLiysLP7qO6PGJyIC8+DQrKudPD1eK8uLj2uaS+39Do0qrXotLio6zSqs/IteO799Do0qrUvMr4tcTK0828o6zIu7rz1Nm147v3xLO49rmkvt+hozxiciAvPg0KttTT2rXa0ru49rmkvt+jrNDo0qrPyMir0aHQ6NKqvNPI67W9c3RhY2tWaWV3tcTK0828o6zIu7rzteO797Xa0ru49rmkvt+hozxiciAvPg0KttS24Lj2ytPNvL340NC24NGho6y/ydLUsLTXoXNoaWZ01NnW8Lj2tePRodKq0aHW0LXEytPNvKGj0rK/ydLUsLTXoUFsdLz8o6zSu7TO0NS/8tGh0OjSqtGh1tC1xMrTzbyhozxiciAvPg0KttTT2rXatv649rmkvt/Q6NKq16LS4qOs08nT2rjDuaS+38rHudjT2rbUxuu1xKOsyOe5+8rH0qrJ6NbDxLO49srTzbzP4LbU09q4+crTzby1xLbUxuu52M+1o6zWu9Kq0aHU8dXiuPbK0828o6zIu7rzteO797Xatv649rmkvt+ho8jnufvSqsno1sO24Lj2ytPNvNauvOS1xLbUxuS52M+1o6zQ6NKqz8i9q9Xi0KnK0828zazKsdGh1tCjrMi7uvPU2bXju/e12rb+uPa5pL7fo6y9+Lb4vfjQ0Mno1sOhozxiciAvPg0KPGltZyBhbHQ9"這裡寫圖片描述" src="/uploadfile/Collfiles/20160420/20160420093535544.png" title="\" />
上圖就是設置某個視圖相對於父視圖的對其關系。勾選的是有效的,未勾線的是無效的。horizontally in container是視圖中心的水平位置偏離父視圖中心水平位置的量。往左是負值,往右是正值。Vertically in Container是垂直方向。
第三個工具中管理視圖與相鄰視圖間距的地方,有一個constrain to margins,如果勾選,表明這些數值相對於內邊界,如果不勾選,就相對於根視圖的邊界(邊緣)。
還有,上面途中左邊和上面的數值是生效的,右邊和下面的數值不生效。也就是說線的虛實決定是否生效。
第四個工具分為兩欄,一欄是selected Views,一欄是All Views in View Controller,前者表明操作的對象為選中的視圖,後者表明操作的對象是這個視圖控制器中的所有視圖。Update Frame就是根據現有的約束更新視圖的frame。Update Constraints就是根據當前視圖布局來更新約束。
(1)方法一:xcode左側
(2)方法二:查看某個視圖的約束,先點擊該視圖,再點擊xcode右側的第五個圖標,就可以看到關於該視圖的所有約束(下圖)。
(3)方法三:通過小標簽或者線條:
選中要查看的視圖,然後可以看到他四周出現了小標簽和線條(代表約束),點擊它們就能在右側查看對應的約束。
標簽和線的顏色是有意義的:
通常綠色表示對這個視圖的約束可以唯一確定這個視圖的布局。
紅色表示仍未能確定唯一確定這個視圖的布局,或者這個約束有沖突。
橘色表示該約束確定的是視圖相對於根視圖的位置。
(1)按查看約束的第一種方法或第三種方法查看約束,點擊需要編輯的約束。再到xcode右側,點擊(下圖)第四或第五個圖標,就可以看到約束的編輯區域。
(2)按查看約束的第二種方法查看約束,點擊約束旁邊的edit按鈕就可以編輯約束。
不論用interfaceBuilder還是用代碼來實現自動布局,這些建議都是適用的。
(1)不用view的frame、bounds、center來指定view的形狀
(2)盡可能地使用stackView來布局
(3)約束盡量建立在view和其相鄰view之間
(4)避免給view指定固定的長和寬
(5)自動更新view的frame時要留心,尤其是對於約束條件不足的view。
(6)view的命名要有意義,方便布局時認得它們。
(7)使用leading和trailing約束,不要用left和right。
(8)在寫相對於view邊界的約束的時候,分兩種情況:
水平方向上的約束:對於大多數的控件,約束應該相對於根視圖的內邊界
對於像小說閱讀器這樣文字布滿屏幕的情況,約束應該相對於文本邊界。
對於需要鋪滿根視圖寬度的視圖,約束可以相對於根視圖的邊界。
垂直方向上的約束:如果根視圖有被導航欄、tabBar等部分遮擋了,那麼約束應該相對於top margin和bottom margin。
(9)在使用autolayout來布局那些用代碼創建的view的時候,要把他們的translatesAutoresizingMaskIntoConstraints屬性設置為NO。這個屬性如果設為YES,系統會自動為這些view生成一些約束,這些約束可能會和我們設置的約束產生沖突。
用代碼創建約束有三種方法:使用NSLayoutConstraint類,使用布局anchor(錨),使用Visual Format Language可視化格式語言。
在約束的原理中有說道,約束方程的形式是這樣的,item1.attribute1 = multiplier × item2.attribute2 + constant。它的組成有7部分:item1、attribute1、item2、attribute2、relationship、multiplier、constant。NSLayoutConstraint類的功能在於,利用約束方程7個量來構造約束方程,並讓他成立。直接上例子最直觀:
NSLayoutConstraint(item: myView, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .LeadingMargin, multiplier: 1.0, constant: 0.0).active = true
如上就建立了一個約束:myView.leading=view.leadingMargin×1.0+0.0。
這個方法來創建約束是個老方法,蘋果官方推薦,iOS 9.0之後出現的新方法,使用anchor來創建約束,看下一點。
使用anchor的原理本質上和使用NSLayoutConstraint類是一樣的,但是表達更加簡潔了。
具體看NSLayoutAnchor類。
直接看官網文檔吧Visual Format Language,那麼直觀。
初學自動布局,目前覺得,實現autoLayout還是使用interface Builder提供的四個工具最方便。因為這四個工具可以一次性創建多條約束,用代碼的話得一條條地寫。
自動布局還有一種方法是用stackView,用它還可以實現動態增減視圖。