Auto Layout 和 Constraints
文章修改
2月1日:添加使用約束、編輯約束和iOS特性三個部分
2月24日:根據自己的理解,修改iOS特性部分的內容
自動布局Auto Layout
Auto Layout,通過設置在View上的約束,動態計算視圖層次結構中所有的View的尺寸和位置。舉個栗子,你約束一個Button,令它的水平中心線和一個ImageView相同,并且它的上邊緣距離ImageView的下邊緣有8個像素。如果ImageView的尺寸或者位置改變,Button會自動調整,以符合之前設置的約束。
基于約束的Auto Layout,使我們搭建能夠動態響應內部和外部變化的用戶界面。
外部變化
外部變化發生于superview的尺寸或者位置改變,比如,
設備屏幕旋轉;
支持不同屏幕大小的設備。
這時,所有的View都要重新計算尺寸和位置。每一次變化,都會刷新視圖層級結構的布局。這些變化大部分發生在運行時,它們需要APP能夠動態響應。
內部變化
內部變化發生于你的界面中的View的尺寸或者位置發生改變。比如,
APP中顯示的內容的改變,新的內容可能需要一個新的布局。一般,在顯示文字或者圖片時會出現這種情況;
APP支持動態設置。如果用戶可以設置字體大小,這將會改變任何與文本相關的控件的高度或者寬度,布局必須能夠適應變化。
約束Constraints
Auto Layout的實現是基于設置在View上的一系列約束的。每一條約束都是一個表達式。
下圖是官方文檔給的示例圖:
這個約束表明,Red View的左邊緣與Blud View的右邊緣的距離為8。這個等式由以下幾個部分組成:
Item 1 :表達式中的第一個控件。在這個例子中是Red View;
Attribute 1 :第一個控件的一個屬性。在這個例子中是Red View的leading edge;
Relationship :表達式左右兩邊的關系,可以使等于,大于等于或者小于等于。在這個例子中,兩邊的關系是相等的;
Multiplier :和第二個控件的屬性相乘的乘數,是一個浮點型。在這個例子中是1.0。一般情況下,這個值不可置為0.0;
Item 2 :表達式中的第二個控件。在這個例子中是Blue View。它是可以為空的,即表達式變成Item 1 * Attribute 1 = 0.0 * NotAnAttribute + Constant;
Attribute 2 :第二個控件的一個屬性。在這個例子中是Blue View的trailing edge;
Constant :一個浮點型的常數。在這個例子中是8.0。
大部分的約束是定義兩個控件之間的關系。這些控件必須是View或者是Layout Guide。約束也可以定義一個控件的兩個屬性之間的關系,比如設置一個控件的上邊緣到下邊緣的距離、左邊緣到右邊緣的距離,即它的高度或者寬度。當表達式中的Item 2為空時,它的屬性必須被設為Not An Attribute,并且Multiplier置為0.0。
約束中用到的屬性
通常情況下,包含四個邊(leading,trailing,top和bottom),以及高度(height),寬度(width),水平中心點(CenterY),垂直中心點(CenterX)。文本類型的控件還有一個基線(baseline)屬性。
屬性說明
Height和Width。這兩個屬性可以被直接賦值,可以是一個常數,也可以是其他View的Height或者Width值。但是,不可以為負數。
Top、Bottom、Baseline。可以和Top、Bottom、Baseline、CenterY組合。
Leading和Trailing。可以和Leading、Trailing、CenterX組合。
Left和Right。避免使用這兩個屬性,而使用Leading和Trailing來替代它們。
CenterX和CenterY。CenterX可以和Leading、Trailing、Left、Right組合。CenterY可以和Top、Bottom、Baseline組合。
使用屬性定義約束
上面提到的屬性可以分為兩類,尺寸相關和位置相關。尺寸相關(如height、width)用來定義物件的大小。位置相關(如leading,top)的屬性用來表明該物件和其他物件之間的位置關系。使用這些屬性時需要注意:
不要使用尺寸相關的屬性去約束一個位置相關的屬性。
只可以給尺寸相關的屬性直接賦值一個常量。
舉幾個簡單的例子:
// 設置一個固定高度 View.height = 0.0 * NotAnAttribute + 40.0// 設置兩個按鈕之間的固定距離 Button_2.leading = 1.0 * Button_1.trailing + 8.0// 讓兩個按鈕的左邊緣對齊 Button_1.leading = 1.0 * Button_2.leading + 0.0// 給兩個按鈕相同的寬度 Button_1.width = 1.0 * Button_2.width + 0.0// 讓View的中心和父類的中心相同 View.centerX = 1.0 * Superview.centerX + 0.0 View.centerY = 1.0 * Superview.centerY + 0.0// 設置一個View的寬高比 View.height = 2.0 * View.width + 0.0約束的設置沒有最好的,只有最適合的。
約束的優先級
優先級priority是Auto Layout在計算的時候用到的參數。優先級的值可以是1-1000任意整數。系統定義了low(250)、medium(500)、high(750)和required(1000)四個等級。一般情況下,我們手動設置的優先級的值也會集中在這四個等級下。
優先級默認值是1000。
關于Auto Layout是如何通過優先級來計算出解決方案,我在看完官方文檔后還是一頭霧水。希望有大神可以指點一二。
使用約束
添加約束
在storyboard中有3種方式添加約束。
在View之間使用Control-Drag;
使用Pin和Align工具;
讓Interface Builder自動添加約束。
Control-Dragging
所謂Control-Draging就是按住Control鍵,用鼠標左鍵拖動的方式添加約束。這兩步操作也可以用按住鼠標右鍵拖動來替代。
當釋放鼠標左鍵后,就會彈出一個HUD,顯示可以設置的約束。
Interface Builder會根據選擇的兩個控件以及拖動的方向篩選出可以設置的約束。如果拖動的方向傾向于水平,你可以選擇設置水平方向上的間距和垂直方向上的對齊方式。反之,如果拖動的方向傾向與垂直,則可以選擇設置垂直方向上的間距和水平方向上的對齊方式。
提示:
可以從一個控件拖動到另一個控件,設置它們之間的關系。也可以拖動到控件自身,設置寬度和高度;
不僅可以在Scene中直接拖動,可以在Storyboard左側的視圖大綱中用同樣的方式拖動。在大綱中拖動設置約束,會顯示出所有的可選約束,而不會進行篩選;
Control-Dragging可以非常快速得設置約束。這些約束是基于Scene中View的當前的位置,因此在設置約束之前要定位好View。
使用Stack、Align、Pin、Resolve工具
Interface Builder在Storyboard的編輯窗口的右下角提供四個自動布局的工具,分別是Stack、Align、Pin、Resolve Auto Layout Issues。
當你想精確控制約束的Constant或者想一次性添加多個約束,可以使用Align和Pin工具。使用Align和Pin還有一個好處,我們不是必須要設置好View的位置,而是只需要定好相對位置,添加約束,然后update frames。Auto Layout會自動計算出正確的位置。
Stack Tools
Stack Tools可以將選中的一個或者多個控件嵌入到一個Stack View中,并會重新計算布局。Stack View是iOS 9添加的新特性。
對于Stack View,我還沒有弄明白使用方法,所以這里不講述。
Align Tool
Align Tools可以快速對齊控件。選擇一個或多個你想對齊的控件,然后單擊Align Tool。然后會彈出可選的一系列對齊方式。
選擇其中的選項,然后點擊Add Constraints。之后,就會自動添加對齊的約束設置。大部分情況下,會選擇兩個或者兩個以上的View來設置對齊。Horizonally in Container和Vertically in Container這兩個可以添加到單一的View上。
Pin Tool
Pin是大頭針的意思。所以這個工具可以用來給View定位。它可以讓我們快速設置一個View相對于它周邊View位置或者它的寬高。選擇一個你想對其進行定位的View,單擊Pin Tool,會彈出如下的窗口。
窗口的上半部分,可以設置選中的View的Top,Bottom,Leading,Trailing與相鄰最近的View的間距。最初顯示的數字是當前的間距。我們可以輸入一個自定義的值,還可以點擊輸入框右邊的倒三角,在彈出的下拉菜單中選擇參照的View。關于Constrain to margins選項,如果選中,會將父視圖的外邊距作為間距的值的參考。
下半部分可以設置寬高相關的屬性。寬和高默認的是Scene中的尺寸,也可以自定義值。寬高比的默認值也是根據Scene中的尺寸進行計算。如果想自定義的話,只有在設置完寬高比之后修改這個約束。
一般情況下,選擇一個View,對它進行定位。選擇兩個及其以上的View設置Equal Height或者Equal Width。使用Pin Tool設置完約束后,可能需要Update frames。
Resolve Auto Layout Issues
Resolve Auto Layout Issues提供一些解決Auto Layout問題的方法。上半部分只針對選中的View,下半部分則針對Scene中所有的View。
我們可以
根據當前約束更改frame;
根據當前frame更改約束的設置;
添加缺少的約束;
清除已添加的約束;
設置系統推薦的約束。
這些功能字面上寫得很清楚,具體的效果大家可以用簡單的Demo來看一下。
讓Interface Builder為我們設置約束
Interface Builder可以為我們創建部分或者全部的約束。根據所提供的View的尺寸和位置,它會推斷出最好的約束。前提是,我們必須確定View的位置并不再更改。一個小小的間距的改變,可能對于整個布局來說確實巨大的。
如果想讓Interface Builder來完成約束的添加,單擊上文提到的Resolve Auoto Layout Issues工具,點擊Reset to Suggested Constraints。Interface Builder就會為已選的(也可以是Scene中全部的)View創建合適的約束。
另外,我們可以自己添加一部分約束,然后選擇Add missing Constraints,讓Interface Builder來添加剩下的所需的約束。
這種方法可以快速完成約束的設置。但是,有可能運行得到的UI并不是你想要的。要不斷地測試UI,修改約束,以達到最終想要的效果。
編輯約束
添加約束之后,需要能夠找到它、查看它、編輯它。
在Scene里查看約束
編輯窗口會顯示作用于當前選擇的View的約束。通過線的形狀、顏色和類型說明當前約束的當前狀態。
I-bars(兩端是T型的線):I-bars顯示間距的大小。可能是兩個控件之間的大小,可能是一個控件的高度或者寬度。
Plain Line(一條普通的直線):Plain Line顯示控件邊緣的對齊方式。例如,兩個或兩個以后的控件是左對齊的,那么,這條線會連接著這些控件,并且與它們的Leading之間的間距為0。
Solid Line:實線表示這個約束是Required,即priority == 1000。
Dash Line:虛線表示這個約束是Optional,即priority < 1000。
Red Line:紅線表示被約束的影響的控件的約束設置有錯誤。具體的原因可以點擊大綱中每個Scene的右邊的箭頭查看。這時,箭頭是紅色的。
Orange Line:橘黃色的線表明,Auto Layout根據已有約束計算出來的frame和當前Scene中設置的frame不同。這時,大綱中的右邊的箭頭是黃色的。可以用Resolve Auot Layout Issues -> Update frames來進行修正位置。
Blue Line:藍色的線表示當前的約束設置是正確的,并且控件的位置和Auto Layout計算出來的位置是一樣的。
Equal Badges:相等標記表明兩個控件的寬度或者高度是相同的。并且標記中包含=符號。
Greater-than-or-equal and less-than-or-equal badges:和Equal Badges類似,它們是標記約束的關系是大于等于或者小于等于的,同時也會顯示對應的符號。
快速找到并編輯添加的約束
所有添加的約束都陳列在大綱里。這些約束以偽代碼的形式呈現。當選擇一個約束時,會在Scene中高亮顯示,可以幫助我們快速找到它。而且,可以在右側的Show the Size inspector下對約束的Constant、Priority、Multiplier、Relation、Identifier、Placeholder屬性進行編輯。
一旦UI變得復雜之后,我們用這種方式找約束就會顯得很吃力。Show the Size inspector工具可以顯示出在當前選中的控件上添加的約束。約束的一部分屬性也可以在這里進行修改。
注意:這里雖然都是在Show the Size Inspector下進行修改,但是前者是選擇一個約束,后者是選擇一個控件。
Auto Layout在iOS中的特性
iOS在與Auto Layout方面有一些獨有的特性,包括top and bottom layout guides、Layout Margins。
Top and Bottom Layout Guides
top and bottom layout guides表示當前的ViewController從最上面到最下面的可見范圍。如果不希望顯示的內容在UIKit bars(例如status bar,navigation bar,tab bar)下面,那么就可以和上下的layout guide來設置約束。
layout guides是遵守UILayoutSupport協議的。這個協議有一個length屬性,來表示guide和root view(root view就是ViewController默認添加的view)邊緣的距離:
對于top layout guide,length指明ViewController的root view的上邊緣和覆蓋在root view上的bar(例如status bar和navigation bar)的底部的距離。
對于bottom layout guide,length指明ViewController的root view的下邊緣和覆蓋在root view上的bar(例如tab bar)的頂部的距離。
在iOS 9中,guide也可以像控件一樣,支持用top、bottom、height設置約束。比如,用top layout guide的bottom屬性和bottom layout guide的top屬性與view設置約束。UILayoutSupport還提供了topAnchor、bottomAnchor、heightAnchor屬性,可以讓我們用代碼的形式來設置約束。
如果layout guides是view最近的"控件",那么系統會自動將layout guides作為設置約束的對象。當使用Pin Tool時,可以在layout guides和root view的上下邊緣進行選擇。
Layout margins
Auto Layout為每一個view都定義了margin。margin指的是控件顯示內容部分的邊緣和控件邊緣的距離。就像“回”這個漢字一樣,外面的“口”就是控件的外邊緣,里面的“口”是控件顯示內容的部分的邊緣,我暫且稱它為內邊緣,這兩個邊緣之間的距離就是margin。
可以用layoutMargins或者layoutMarginsGuide屬性獲得view的margin。layoutMargins允許獲取或者設置UIEdgeInsets結構的margin,layoutMarginsGuide則只會獲取到只讀的UILayoutGuide對象。
每一個view的默認的margin是8。可以根據APP的需要進行修改。__系統給ViewController的root View設置的margin則不能修改__。root View的上下的margin為0,左右的margin為20。
當使用Control-Dragging方式,給一個View和它們父視圖設置約束時,默認使用內邊緣,而不是外邊緣。當使用Pin Tool時,如果Constraint to margins被勾選,則會使用父視圖的內邊緣作為設置約束的參照,如果沒有被勾選,則使用外邊緣作為設置約束的參照。所見到的效果就是兩種參照下的約束的Constant的值相差一個margin。
當在Interface Builder中編輯約束時,First Item和Second Item的彈出菜單中可以選擇Relative to margin。如果勾選,會在top、leading等屬性后面加上Margin,變成topMargin、leadingMargin等,意味著約束的設置參照View的內邊緣而不是外邊緣。
設置約束時的一些建議
官方文檔為我們提供了一些設置約束時的建議
__在最相近的兩個控件之間創建約束。__假如有3個Button,分別是first,second,third。可以約束first的右邊緣和second的左邊緣的距離,second的右邊緣和third的左邊緣的距離。而不要約束first的右邊緣和third的左邊緣距離,中間隔了一個second。
__避免設置固定的高度和寬度。__Auto Layout是動態響應布局的變化。一個控件如果設置固定尺寸,那么就會失去自動調整的能力。
在update frames時注意,如果這個控件沒有設置足夠多的約束來確定它的位置和尺寸,那么可能會導致一些意想不到的后果。比如會跑到屏幕外,或者因為寬度、高度為0而消失等等。
給所有的控件取一個有意義的名稱。在使用工具時,可以方便地辨別出View。
使用leading和trailing代替left和right。
當使用代碼創建約束時,確保將它們的translatesAutoresizingMaskIntoConstraints屬性為NO。默認情況下,系統會基于控件的frame自動創建一系列約束。當你添加自己的約束時,不可避免地會和自動生成的約束產生沖突。
參考內容:
蘋果官方文檔:Auto Layout Guide
本文中的圖片均取自蘋果官方文檔
總結
以上是生活随笔為你收集整理的Auto Layout 和 Constraints的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到同班异性是为什么
- 下一篇: tkinter的GUI设计:界面与逻辑分