自動佈局-Constraint

springs和struts的問題
你肯定很熟悉autosizing masks-也被認爲是springs&struts模式。autosizing mask決定了當一個視圖的父視圖大小改變時,其自身需要做出什麼改變。它有一個靈活的或固定不變的margins(struts)嗎?它的寬和高要做出什麼改變(springs)?
 
舉個例子,一個寬度靈活的視圖,如果其父視圖變寬,那麼它也會相應的變寬。一個視圖右邊擁有固定的margin,那麼它的右邊緣將會一直粘住其父視圖的右邊緣。
 
autosizing系統在簡單的情況下非常奏效,但當你佈局變得更復雜時,它立馬跪了。讓我們看一個springs和struts不能處理的示例。
 
打開Xcode5,創建一個基於Single View Application模板的iPhone項目。叫做"StrutsProblem":

點擊Main.storyboard。在你做別的之前,首先將這個storyboard的自動佈局關了。你需要在File inspector,第一個選項的第六個tabs裏:

 
將Use Autolayout的box勾選去掉。現在storyboard使用舊的struts-and-springs模型。
 
注意:任何你使用Xcode4.5或更高版本中,nib或者storyboard文件都默認激活了自動佈局。因爲自動佈局是iOS6以及以上系統的一個新特性,如果你想使用最新的Xcode開發兼容iOS5的程序,你需要將這個選項去掉。
 
拖拽三個新的視圖到主視圖上,並且像這樣排列起來:

爲了表述更清楚,這裏給出每個視圖的顏色,這樣你就能分清哪個是驢子哪個是馬了。
每個視圖的inset到窗口的距離都是20點;視圖之間的距離也是20點。底部的視圖的寬是280點,上面兩個視圖的寬都是130點。所有的視圖的高都是254。
 
 
在iPhone Retina 4-inch simulator上運行這個程序,並且將模擬器旋轉到landscape。程序看起來便變成這副鬼樣,這不是我想象的那樣:

注意:你可以使用Hardware\Rotate Left和Rotate Right菜單選項旋轉模擬器,或者通過按下鍵盤上的? 鍵,同時按下←或→。
 
而你想象的程序在landscape應該像這樣:
 
很明顯,三個視圖的autosizing masks留下了一些需要改進的地方。將左上方視圖的autosizing設置改成這樣:
這將會讓視圖貼附左上邊緣(不是右下邊緣),並且當父視圖大小改變時,重新調整自身水平和垂直方向的大小。
 
同樣的,右上方視圖的autosizing設置改成這樣:


底部視圖:

再次運行程序,並且旋轉到landscape。現在看起來像這樣:

已經很接近了,但又不完全一樣。視圖之間的padding不正確。換個說法就是視圖的大小不完全正確。問題出在當父視圖改變大小時,autosizing masks告訴子視圖調整大小,但又沒告訴子視圖該調整多少(坑兒?)。
 
你可以調戲autosizing masks-比如,改變靈活寬度和高度設置(springs)-你不會得到完全正確的三個間距20點的視圖。

爲了解決這個springs和struts方法的佈局問題,非常不幸,你需要額外寫一些代碼。
 
在旋轉用戶界面之前、之間、之後,UIKit會發送一些消息到你的視圖控制器,你可以截獲這些消息,從而對你UI做出改變。代表性的像viewWillLayoutSubviews,你會重寫這個方法從而改變任何需要重新排列的視圖的frame。
 
在這之前,你需要先做出一個outlet屬性來引用這個視圖。
 
切換到Assistant Editor模式,按住Ctrl,將三個視圖都拖到ViewController.m中去:

 
分別鏈接視圖到這三個屬性:
  1. @property (weak, nonatomic) IBOutlet UIView *topLeftView; 
  2. @property (weak, nonatomic) IBOutlet UIView *topRightView; 
  3. @property (weak, nonatomic) IBOutlet UIView *bottomView; 
 
下面的代碼寫到ViewController.m:
  1. - (void)viewWillLayoutSubviews 
  2.     if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) 
  3.     { 
  4.         CGRect rect = self.topLeftView.frame; 
  5.         rect.size.width = 254; 
  6.         rect.size.height = 130; 
  7.         self.topLeftView.frame = rect; 
  8.   
  9.         rect = self.topRightView.frame; 
  10.         rect.origin.x = 294; 
  11.         rect.size.width = 254; 
  12.         rect.size.height = 130; 
  13.         self.topRightView.frame = rect; 
  14.   
  15.         rect = self.bottomView.frame; 
  16.         rect.origin.y = 170; 
  17.         rect.size.width = 528; 
  18.         rect.size.height = 130; 
  19.         self.bottomView.frame = rect; 
  20.     } 
  21.     else 
  22.     { 
  23.         CGRect rect = self.topLeftView.frame; 
  24.         rect.size.width = 130; 
  25.         rect.size.height = 254; 
  26.         self.topLeftView.frame = rect; 
  27.   
  28.         rect = self.topRightView.frame; 
  29.         rect.origin.x = 170; 
  30.         rect.size.width = 130; 
  31.         rect.size.height = 254; 
  32.         self.topRightView.frame = rect; 
  33.   
  34.         rect = self.bottomView.frame; 
  35.         rect.origin.y = 295; 
  36.         rect.size.width = 280; 
  37.         rect.size.height = 254; 
  38.         self.bottomView.frame = rect; 
  39.     } 
當視圖控制器旋轉到一個新的方向,這個回調將會被調用。它會監控視圖控制器旋轉的方向,並且適當的調整視圖大小-在這種情況,根據已知iPhone屏幕大小會有一個hard-code(將可變變量用一個固定值來代替的方法叫做hard-code)偏移。這個回調會在一個動畫block中發生,所以會動態的改變大小。
 
暫時還不要運行這個程序。首先你需要按下面的樣子重新保存三個視圖的autosizing masks,否則autosizing mechanism將會和你在viewWillLayoutSubviews中設置的位置和大小衝突。
這樣就可以了,運行程序並且翻轉到landscape。現在視圖排列的非常號。翻轉回到portrait,經覈實,一切都良好。
 
這樣奏效了,但是你需要爲這個非常簡單的例子編寫大量的佈局代碼。想象一下,爲佈局付出的努力是非常複雜的,特別是個別視圖動態的改變大小,或者子視圖的個數是不固定的。
 
現在試着在3.5寸的模擬器上運行程序。我了個去。視圖的位置和大小又錯了,因爲viewWillLayoutSubviews的hard-code座標是基於4英寸大小的手機(320x568取代320x480)。你可以增加另一個if語句判斷屏幕大小,並使用不同的座標集,但是你可以看到這個方法很快變得不切實際。
注意:另一個你可以採取的方法就是爲portrait和landscape模式建立獨立的nibs。當設備旋轉時,你從另一個nib中裝載視圖並替換掉當前的那個。但這任然需要做很多工作,並且維護兩個nibs也會增加問題。當你使用storyboards替代nibs的時候,這個方法也變得不切實際。
 
自動佈局拯救猿!
現在你將會看到如何用自動佈局實現相同的效果,從ViewController.m中移除viewWillLayoutSubviews,因爲我們不再需要寫任何代碼。
 
選擇Main.storyboard,並在File inspector中選擇開啓Use Autolayout:
運行程序,旋轉到landscape。現在看起來像這樣:
讓我們把自動佈局付諸行動。當你點擊頂部兩個視圖時,按住?鍵,這樣兩個視圖都被選中了。從Xcode的Editor菜單中選擇Pin\Widths Equally:
再次選中兩個相同的視圖,選擇Editor\Pin\Horizontal Spacing。(儘管你執行完第一次Pin處理後,兩個視圖看起來還是被選中的,但其實他們只是在一個特別的佈局關係顯示模型裏。所以你需要重新選擇這兩個視圖)
 
storyboard現在看起來像這樣:
橙色的"T-bar"形狀代表視圖間的約束。目前爲止你增加了兩個約束:一個等寬約束和一個位於兩個視圖間的水平約束。約束表達了視圖之間的關係,並且他們是你使用自動佈局建立佈局最主要的工具。這貨看起來有點嚇人,但是一旦弄懂它的意思,便變得相當簡單。
 
爲了繼續爲這個屏幕簡歷佈局,執行下面這些步驟。每個步驟增加更多橘黃色的T-bars.
 
o Top Space to Superview
o Leading Space to Superview
For the view on the right, choose:
o Top Space to Superview
o Trailing Space to Superview
And for the big view at the bottom:
o Leading Space to Superview
o Trailing Space to Superview
o Bottom Space to Superview
 
現在你應該有了下面這些約束:
注意T-bars仍然是橘黃色的。這意味這你的佈局沒有完成;自動佈局沒有足夠的約束條件計算出視圖的位置和大小。解決辦法便是增加更多約束,直到他們變藍。
 
按下? 鍵並選中三個視圖。從Editor菜單中,選擇Pin\Heights Equally。
 
現在選中左上角的視圖和底部視圖(像前面一樣按住鍵),選擇Editor\Pin\Vertical Spacing.
 
Interface Builder看起來應該像這樣:
T-bars已經變藍了。自動佈局現在已經有足夠的信息來計算出一個有效的佈局。這看起來有點雜亂無章,這是因爲等寬和等高約束條件佔去了很大空間。
 
運行程序並且...我說吧,不需要寫一行代碼便運行的很好了。不管你在哪個模擬器上運行;在3.5英寸和4英寸設備上,佈局都運行良好。
這非常酷,但是究竟你在這做了什麼?自動佈局讓你表達出佈局中的視圖和其他每個視圖的關係,而不是需要你指出視圖有多大,放在哪兒。你需要放置以下這些關係(即我們所謂的約束)到佈局中:
1.左上角和右上角的視圖總是有相等的寬度(也就是pin中第一個widths equally命令)。
2.左上角和右上角的視圖水平方向有20點距離(也就是pin中的horizontal spacing)。
3.所有的視圖總是有相同的高度(也就是pin中heights equally命令)。
4.上面兩個視圖和下面那個視圖垂直方向上有20點距離(也就是pin中的vertical spacing)。
5.視圖和屏幕邊緣有20點空間(top,bottom,leading和trailing space相對於父視圖的約束)。
 
這些就足以表達出自動佈局該怎麼放置視圖,以及當屏幕大小改變時該如何處理。
你可以在左邊Document Outline中看到你所有的約束,組名叫做Constraints(當你爲storyboard激活自動佈局時纔會加進來)。
 
如果你在Document Outline中點擊一個約束,Interface Builder將會在視圖中高亮出它:
 
約束是一個真實的對象(NSLayoutConstraint),並且他們也有屬性。比如,選擇上面兩個視圖的間距約束條件(叫做"Horizontal Space(20)"),然後切換到Attributes inspector。你可以在那裏通過編輯Constant字段改變邊緣空間的大小。
將它設置爲100,然後再次運行程序。現在他們邊緣空間變得更寬了:
自動佈局在描述視圖上比springs和struts顯得更有表現力。在這篇教程的剩餘部分,你將會學到約束的一切,以及如何將他們應用到Interface Builder上來構造出不同種類的佈局。
 
自動佈局如何工作
正如你在上面測試樣例中所看到的一樣,自動佈局最基本的工具是約束。一個約束描述了兩個視圖間的幾何關係。比如,你可能有這樣一個約束:
"label A右邊緣和button B左邊緣有20點的空白空間。"
 
自動佈局會考慮到所有的約束,然後爲你的視圖計算出理想的位置和大小。你再也不需要親自爲你的視圖設置frames了-自動佈局會完全基於你爲這些視圖設置的約束爲你做這個工作。
 
自動佈局以前,你一直需要爲視圖的frames設置hard-code,要麼在Interface Builder中將他們放置在特定的座標,或通過傳遞一個rectangle到initWithFrame:,或者設置視圖的frame,bounds或者center屬性。
 
就你剛剛做的那個程序,你需要明確設置frames爲:
還需要爲這些視圖設置自動調整大小的masks:
 
這再也不是你需要爲屏幕設計所考慮的東西了。使用自動佈局,你需要做這些:
視圖的大小和位置再也不重要了,只有約束要緊。當然,當你拖一個新建的button或label到畫布上時,它會有一定的大小,並且你會將它拖到某一位置,但這是隻一個用來告訴Interface Builder如何放置約束的設計工具。
 
 
想你所想,如你所願
使用約束最大的優勢就是你再也不需要把時間浪費在座標上了。相反,你可以向自動佈局描述視圖如何和其他視圖相關聯,自動佈局將會爲你完成所有困難的工作。這叫做根據目的設計(designing by intent)。
 
當你根據目的設計時,你表達的是你想要實現什麼,而不需要關心它如何實現。"button的左上角座標爲(20,230)",現在你可以這麼說了:button是垂直居中於它的父視圖,並且相對於父視圖的左邊緣有一個固定的距離。
 
使用這個描述,不管父視圖多大或多小,自動佈局都可以自動計算出你的button需要在哪兒出現,
 
其他根據目的設計的示例(自動佈局可以處理所有這些指令):
"這兩個text fields的大小需要一直相等。"
"這兩個button需要一直一起移動。"
"這四個labels需要一直右對齊。"
 
這使得你用戶界面的設置更具描述性。你只需簡單的定義約束,系統會爲你自動計算frames。
 
在第一部分你看到,即使爲幾個視圖在橫豎方向上正確的佈局都需要做大量的工作。有了自動佈局,你可以繞過這些麻煩。如果你正確的設置了約束,那麼在橫豎屏方向上,佈局將不需要做任何改變。
 
使用自動佈局另一個重要的好處就是本地化。比如德語中的文本,出了名的比老奶奶的裹腳布還要長,適配起來是一件很麻煩的事。再次,自動佈局拯救了猿,因爲它能根據label需要顯示的內容自動改變label的大小。
 
現在增加德語,法語或者其他任何一種語言,都只是設置約束的事,然後翻譯文本,然後。。。就沒有然後了!
獲得自動佈局竅門最好的方法就是使用它,所以這正是剩下教程中你會學到的東西。
 
注意:自動佈局不僅對旋轉有作用;它還能輕易的縮放你UI的大小從而適應不同尺寸的屏幕。這並不是巧合,當iPhone5擁有更高屏幕的同時,這個技術也同時加到了iOS中!自動佈局能輕易的拉伸你程序的用戶界面,從而充滿iPhone5垂直方向上多出來的空間。隨着iOS7中的動態類型,自動佈局變得更加重要了。用戶現在可以改變全局字體大小設置--有了自動佈局,這將變得非常簡單。
 
 
擁抱約束(courting constraints)
關閉你當前的項目並用Single View Application模板創建一個新的iPhone項目。叫做"Constraints"。任何用Xcode5創建出來的項目都會自動假定你會使用自動佈局,所以你並不需要額外做任何事情。
 
點擊Main.storyboard打開Interface Builder。拖一個新的Button到畫布上。注意當你拖拽的時候,藍色虛線將會出現。這寫線用來做嚮導。
在屏幕邊緣以及中心的時候,都會有嚮導線:
 
如果之前你已經使用過Interface Builder,那麼你肯定看到過這些嚮導線。這對我們對齊控件有很大的幫助。
 
在Xcode4中激活自動佈局時,嚮導線有另外一個目的。你任然可以用他們來對齊,但是他們也會告訴你新的約束將會在哪兒。如果你將button沿着嚮導線反方向拖拽到左上角時,Xcode4中的storyboard看起來便像這樣:
有兩個藍色的東西附屬在button上面。這些T-bar形狀的對象便是約束了。Xcode 4的Interface Builder中不管你將UI控制器放在哪兒,它總是會給出有效的約束。理論上這聽起來是個好主意,但是實踐起來,在Interface Builder中使用自動佈局卻非常困難。
 
幸運的是,Xcode5中已經有所好轉。將button拖拽到畫布上之後並看不到T-bars形狀的東西:
 
同時在Document Outline面板中也沒用Constraints部分。得到結論:此時button上並沒有設置任何約束。
 
那這是如何運作的呢?我們之前瞭解的自動佈局總是需要足夠多的約束才能決定視圖的大小和位置,但是現在我們這兒跟本沒有約束。確定這是一個完整的佈局?
 
這這是Xcode5相對Xcode4來說最大的一個提升:再也不強制你總是有一個有效的佈局。
 
注意:1.運行一個無效佈局的程序是不明智的,因爲自動佈局不能正確的計算需要將視圖放在哪兒。要麼視圖的位置是不可預知的(約束不夠),要麼程序將會崩潰(約束過多)。
 
2.Xcode4設法保證總是有足夠多正確的約束來創建一個有效的佈局。不幸的是,它經常會將你的約束替換爲你並不想要的。這會令人很沮喪,正是因爲這個原因很多開發者放棄了自動佈局。
 
3.Xcode5中,當你編輯Storyboard時它允許你有不完整的佈局,但它也會指出哪些地方你還需要修改。使用Interface Builder創建的自動佈局驅動用戶界面變得更有趣了,使用Xcode5也消耗更少的時間。
 
如果你根本不提供任何約束,Xcode自動分配一套默認的約束,正是我們所知的自動約束。它會在程序built的編譯時間中去完成這些事,而不是設計時間。當你設計你的用戶界面時,Xcode5中的自動佈局爲了不參與你的設計方法而努力工作,這這是我們喜歡它的原因。
 
自動約束爲你的視圖提供一個固定尺寸和位置。換句話說,視圖總是擁有跟你在storyboard中看到的一樣的座標。這是非常方便的,因爲這就意味着你可以大量的忽視自動佈局。你可以爲那些擁有充分約束的控件不增加約束,只爲那些需要特殊規則的視圖創建約束。
 
OK,讓我們玩一玩約束並看看他們能做什麼。現在,按鈕是在左上角,並且沒有約束。確認按鈕跟兩個拐角嚮導線對齊。
 
使用Editor\Pin菜單爲按鈕增加兩個新的約束,看起來像這樣:
這是Leading Space to Superview和Top Space to Superview選項。
 
所有的約束都會在Document Outline面板中列出來:
目前有兩個約束,一個是button和main view左邊緣的Horizontal Space約束,一個是button和main view上邊緣的Vertical Space約束。這個關係通過約束描述起來便是:"button總是位於其父視圖左上角20點處。"
 
注意:這些其實都不是非常有用的約束,因爲他們有相同的自動約束。如果你總是想你的button相對於父視圖左上角,那麼你還不如不提供任何約束,讓Xcode爲你做這些。
 
現在拖動button並將它放到屏幕的右上角,再次和藍色嚮導線對齊:
哇哦,這裏發生了什麼?在Xcode4中這會破壞舊的約束並賦值一個基於藍色嚮導線的新約束,但是在Xcode5中button保留了現存的約束。但問題是button在Interface Builder中的大小和位置再也不和自動佈局希望基於約束的大小和位置相符合了。這叫做錯位的視圖。(misplaced view)
 
運行程序。Button仍然會出現在屏幕的左上角:
 
當談到自動佈局,橙色代表壞的。Interface Builder繪製兩個橙色方塊:一個是虛線邊框,一個是實線邊框。虛線方塊是根據自動佈局顯示視圖的frame。實線方塊是根據你在屏幕上放置的視圖的frame。這兩個應該吻合的,但是這裏並沒有。
 
如何修改取決於你想要達到什麼目的:
1.你想讓button附屬於屏幕左邊緣254點處嗎?在這種情況下你需要將現存的Horizontal Space約束變大234點。這正是橙色badge中"+234"的意思。
2.你想讓button附屬於屏幕的右邊緣?那麼你需要移除現有的約束並重新創建一個新的。
 
刪除Horizontal Space約束。首先在畫布或Document Outline中選中,然後按鍵盤上的Delete鍵。
注意這次Vertical Space約束變橙色了。直到現在它都是藍色的。那一個約束並沒有任何錯誤;它的意思是剩下的沒有足夠的約束決定button完整的位置。你任然需要在X軸方向增加一個約束。
 
Note:你可能會奇怪,爲什麼Xcode不爲X軸方向自動增加一個約束。Xcode中的規則是:Xcode只爲那些你沒有設置任何約束的對象創建自動約束。一旦你增加一個約束,你便是告訴Xcode你接管了這個視圖。Xcode將不再增加任何自動約束,並希望你爲這個視圖增加需要的約束。
 
選中button,並選擇Editor\Pin|Trailing Space to Superview.這迫使在button右邊緣和屏幕右邊緣增加一個新的約束。關係表達如下:"button總是位於距離其父視圖右上角20點處。"
 
運行程序並旋轉到landscape。注意button如何與屏幕右邊緣保持相同距離:
 
當你放置一個對立於嚮導線的button(或者任何其他視圖)並新建一個約束時,你會得到一個根據"HIG"(Apple's iOS Human Interface Guidelines document)定義的標準大小的間隔約束。對於邊框來說,標準大小空間是20點。
 
現在將button向左拖拽一點:
由於視圖錯位,你得到了一個橙色虛線邊框。我們假設這個button新位置的確是你想要的。創建完一個約束後做一些細微的調整是很常見的,但這卻會導致橙色方塊出現。一個修改方法就是移除約束並創建一個新的,但還有一個更簡單的解決方案。
 
Editor菜單上有一個Resolve Auto Layout Issues子菜單。從這個菜單中,選中Update Constraints。就我這個情況來說,這會告訴Interface Builder需要將約束變大64點,像這樣:
 
很好,T-bars又變藍了,佈局是有效的。在Document Outline中,你可以看到Horizontal Space約束不再有一個標準的間隔了:
到目前爲止你已經嘗試過了Horizontal Space和Vertical Space約束。還有一個"center"約束。拖拽一個新的Button對象到畫布底部中心,根據嚮導線完好入位:
 
爲了保持button在水平方向上一直居中對齊於父視圖,你需要增加一個Center X Alignment約束。從Editor菜單選擇Align\Horizontal Center in Container.這會增加一個很長的橙色線段:
 
 
線之所以是橙色是因爲你才僅僅指定了button的X軸,但Y軸並沒有指定約束。使用Editor\Pin菜單在button和視圖底部間增加一個Vertical Space約束。看起來像這樣:
 
如果你不知道原因,這是Bottom Space to Superview選項。Vertical Space約束使button遠離視圖底部(再一次使用標準間隔)。
 
運行程序並旋轉到橫屏模式。甚至在橫屏模式,button也保持在屏幕底部的中心:
這就是你表達的意思---這個button始終應該位於底部中心。注意,你根本不需要告訴Interface Builder按鈕的座標是什麼,除非你想將它固定在視圖上。
 
 
通過自動佈局,你再也不需要擔心視圖位置的精確座標或視圖大小了。相反,自動佈局會根據你設置的約束得到這兩個參數。
 
你可以在button的Size inspector中看到這個經典轉移,現在有了很大的不同:
 
如果不使用自動佈局,輸入值到X,Y,Width或Height字段將會改變選中視圖的位置和大小。使用自動佈局後,你仍然可以輸入新值到這些字段,但是如果你已經爲視圖設置了約束,那這可能造成視圖錯位。你將不得不更新約束來匹配新值。
 
舉個例子,把button的寬度改爲100,畫布會變成這樣:
 
Xcode4用Horizontal Space取代Center X Alignment約束,並且button上會產生一個新約束強制它的寬度爲100 points。然而,Xcode5說,"如果你想讓button寬度變爲100 points,對我來說無所謂,但是你要知道約束並不是這麼說的。"
 
在這種情況下你希望button是100點寬。對此有一個特殊的約束類型:Fixed Width約束。首先按一下Undo,這樣button又居中了,T-bars也變藍了。選中button並選擇Editor\Pin\Width。這會在button下面放置一個新T-bar:
 
選中那個T-bar並在Attributes inspector中改變Constant爲100.不管button的title多大或多小,這都會強制button的寬總是100點。爲了能更好的看清你可以給button設置一個背景色:
 
 
你也可以在左邊的Document Outline中看到這個新的Width約束:
 
與其他約束不同,在button和它的父視圖之間,Width約束只會應用到button本身。你可以將這個認爲是一個button本身和本身之間的約束。
 
你可能懷疑爲什麼button之前沒有Width約束。自動佈局是爲何知道button有多寬?
 
事情是這樣的:button自己是知道自己有多寬。它根據自己的title text加上一些padding就行了。如果你爲button設置一個背景圖片,它也會考慮進去。
 
這正是我們熟悉的intrinsic content size。並不是所有的控制器都有這個,但大部分是(UILabel是一個例子)。如果一個視圖可以計算自己理想的大小,那麼你就不需要爲它特別指定Width或Height約束了,你將會在稍後看到更多相關內容。
 
爲了恢復button到最佳大小,首先我們需要移除Width約束。然後選中button,並從Editor菜單中選擇Size to Fit Content。這樣就能夠恢復button的固有的內容尺寸了。
 
孤掌難鳴
嚮導線不但出現在一個視圖和它的父視圖之間,而且也會出現在相同層級的視圖之間。拖拽一個新的button到畫布上進行演示。如果你將這個button拖近其他對象,這時他們的嚮導線將會開始相互影響。
 
將新button放到之前一個button的後面完好入位:

 
這還有一些嚮導虛線。Interface Builder識別出這兩個button可以通過不同方式對齊—頂部,中心以及基線。
 
Xcode4會將這些顯著的嚮導線轉變成新的約束。但是在Xcode5中,如果你想讓這兩個button間有約束,你需要自己創建。之前你已經使用過Editor\Pin菜單來創建這兩個視圖間的約束,但是還有一個更簡單的方式。
 
選中新的button並按住Ctrl拖拽到另一個button上,像這樣:

放開鼠標按鍵,出現一個彈出框。選擇第一個選項,Horizontal Spacing。

這將會創建一個新的約束:
 

 
它是橙色的,這意味着這個button至少還需要另一個約束。button的大小是知道的(使用intrinsic content size),並且還有一個button在X軸上的約束。只剩下Y軸沒有約束了。
 
這種缺失約束的情況是很容易確定的,但是更復雜的設計可能就沒這麼明顯了。幸運的是,你不再需要敏思苦想,Xcode已經記錄並可以確切的告訴你缺少了什麼。
 
在Document Outline中會有一個紅色的小箭頭,就在View Controller Scene後面。點擊這個箭頭便會看到所有Auto Layout問題:

 
我們將Y軸方向缺失的約束加進去。按住Ctrl並向下拖拽新的button:

這次彈出菜單有不同的選項了。這次菜單的選項是基於上下文環境的—你在哪些視圖間拖拽以及鼠標移動的方向。選擇Bottom Space to Bottom Layout。
 
現在新button有一個位於屏幕底部的Vertical Space,也有一個跟其他button相關聯的Horizontal Space。雖然空間非常小(只有8 points),T-bar可能不大容易看到,但它就在那裏。
 
點擊Document Outline裏面的Horizontal Space(8):

 
當你選中一個約束,它會高亮自己所屬的控制器。這個特別的約束位於兩個button之間。這個約束表達了:“不管第一個button在哪兒或多大,第二個button總是出現在第一個button的左邊”。
 
選中黃色背景的button並輸入較長的label,比如:“A longer label”。輸入完成後,button會爲新的text改變大小,並且另一個button會移開。
 
最終,它依附在第一個button的左邊緣,這正是我們所期望的:

 
爲了更好的摸索這是如何工作的,多練一些吧。拖拽另一個button到畫布上並放到黃色button的上方,他們會垂直方向對齊到位(不要試着讓兩個button的左邊緣對齊):

 
爲新button設置一個綠色背景色,這樣就可以更容易看出它的範圍。
 
因爲你將兩個button對齊在一起,現在他們之間存在HIG推薦的8 points間隔。按住Ctrl在兩個button之間拖拽將這變爲一個約束。從彈出菜單中選中Vertical Spacing。
 
Note:“HIG”是“iOS Human Interface Guidelines”的簡稱,包含Apple推薦的良好的用戶界面設計。任何iOS開發者都有必要讀一讀這個規範。HIG解釋了哪些UI元素適合在什麼情況下使用,以及最佳使用方式。你可以在這裏找到。(https://developer.apple.com/library/ios/DOCUMENTATION/UserExperience/Conceptual/MobileHIG/Introduction/Introduction.html
 
然而你並沒有被限制在controls間的標準間隔。約束是成熟的對象,就像視圖一樣,因此你可以改變它們的屬性。
 
選中兩個button之間的Vertical Space約束。你可以在畫布上點擊T-bar,雖然這有點麻煩。目前最簡單的辦法就是在Document Outline裏面選擇約束。一旦你選中約束,再切換到Attributes inspector:

 
在Constant字段裏輸入40改變約束大小。現在兩個button更進一步的分開了,但是他們任然是連接在一起的:

 
運行程序並翻轉到landscape模式查看效果:

 
button必然會保持他們垂直方向的排列,但是水平方向就不了!原因很明顯:綠色button還沒有X軸方向的約束。
 
爲綠色button增加一個到屏幕左邊緣的Horizontal Space並不能解決問題。這樣的約束只會讓綠色按鈕總是保持在同一個X軸座標,即便是在橫屏模式下。這看起來感覺不大對,所以你需要表述這樣一個目的:
“黃色button會一直水平居中,藍色button左邊緣會一直跟黃色button左邊緣對齊。”
 
你已經爲第一種情況創建了一個約束,但是第二個並沒有。Interface Builder爲對齊顯示了嚮導線,這樣你就可以將上面button一直拖拽到跟黃色左邊緣對齊的位置:

 
如果你也在垂直方向上拖拽button,這時button框架和Vertical Space約束之間就不能達到正確的距離了。你在T-bar上將會看到橙色的badge:

如果發生這樣的情況,簡單的使用方向鍵將button微調到位,直到badge消失。
 
最終,按住Ctrl在兩個button間拖拽,從彈出菜單中選擇Left。這會創建一個約束:“兩個視圖的左邊緣一直對齊”。換句話說,這兩個button一直會有相同的X軸座標。這時T-bars變成藍色了:

 
運行程序並旋轉到橫屏模式:

 
何去何從?
現在你已經對自動佈局進行了第一次嘗試,感覺怎麼樣?這可能需要一些時間習慣,但是它能讓你的工作更加簡單,也會讓你的app更加靈活。
 
想要學習更多的內容?繼續閱讀第二部分吧,你將會繼續在Interface Builder中使用button進一步理解Auto Layout提供的多種可能性,以及你可能遇到的問題。
 
最重要的是,你將會使用Auto Layout在一個真實的程序中創建一個逼真的佈局。


(翻譯)開始iOS 7中自動佈局教程(二)

來源:cnblog    閱讀:189   時間:2014-09-20

正文如下:


開始iOS 7中自動佈局教程(一) 你已經看到舊的“struts-and-springs”模型讓user interfaces不能較容易的解決所有的佈局問題。自動佈局是一個解決方案,但是也是因爲它的強大,所以在使用它的時候,我們需要一點小技巧。

值得高興的是,Xcode5讓自動佈局更容易了。如果你在Xcode4中嘗試過自動佈局並且放棄了,那現在我們希望你能再給它一個機會。我們將在Xcode5中使用它。

在第二部分和最後一部分自動佈局的系列教程中,你將要繼續學習所有關於約束的知識以及如何應用它們。

 

千里之行始於足下

這個自動佈局的教程將從下面這個非常簡單的app開始:

app中有兩個設置了背景色的按鈕,你可以更清楚的看到它們的邊界。它們之間有一些約束。如果你學習的上一部分,你可以繼續使用你之前的app,只要你移除界面上的其它兩個按鈕。

如果你打算重新開始,請使用Single View Application模板創建一個新的iPhone應用。拖兩個按鈕到場景中並且給它們設置背景顏色。使用Editor\Pin菜單在兩個按鈕之間創建一個Vertical Spacing約束,然後靠下的按鈕創建一個Bottom Space to Superview約束(大小爲20點)。使用Editor\Align菜單給黃色按鈕創建一個橫向居中的約束,然後兩個按鈕align the left edges(對齊左邊緣)。

在Interface Builder中執行,一切看起來都很棒。但是讓我們看看它們是怎麼工作的。在ViewController.m中加入下面的方法:


- (IBAction)buttonTapped:(UIButton *)sender
{
    if ([[sender titleForState:UIControlStateNormal] isEqualToString:@"X"]) {
        [sender setTitle:@"A very long title for this button" 
                forState:UIControlStateNormal];
   } else {
        [sender setTitle:@"X" forState:UIControlStateNormal];
   }
}

給長標題和短標題按鈕都設置單擊觸發事件。在Interface Builder中將按鈕都連接到action方法上。按住ctrl拖拽每個按鈕到view controller並且在彈出窗中選擇buttonTapped。

運行這個app並且單擊按鈕,看看它是怎麼變化的。可以在橫屏和豎屏都進行測試。


不管按鈕是長標題還是短標題,佈局總是被安全的約束着:

  • 靠下的按鈕總是在視圖中橫向居中
  • 靠下的按鈕總是距離視圖底部20點
  • 靠上的按鈕總是和靠下的按鈕左對齊,並且它們之間的距離總是40點。

上面三點就是你的user interface中所顯示的全部說明

讓我們來試試,移除左對齊約束(在outline窗口中選擇並且按下Delete鍵),然後選中在Interface Builder裏的所有按鈕,在對齊菜單中選擇對齊右邊緣。現在再次運行你的app觀察與之前的區別。

我們再來一次,這次選擇Align\Horizontal Centers。它將使兩個按鈕總是居中對齊。運行app,點擊按鈕,看看它們是怎麼運行的。(記住,如果你改變約束時看到了橘黃色的虛線矩形,你可以使用 Editor\Resolve Auto Layout Issuesmenu來更新按鈕的大小和位置。)

處理寬度

Pin菜單有一個選項是Widths Equally。如果你在兩個view中設置了這個約束,自動佈局將總是讓所有view的寬度等同於最大的那個view的寬度,讓我們來試試。

選中所有按鈕,並且選擇Editor\Pin\Widths Equally。這就給所有按鈕都加上了一個新約束:


在這之前,你可以先看看教程第一部分的關於約束類型的內容。它看起來就像一個T字架,但在中間有一個寫着“=”號的圓。

當然,也許是兩個T字架,在Outline文檔中顯示了一個單獨的Equal Widths約束:


改變按鈕上的label文本,兩個按鈕的尺寸都會同時改變。將下面的按鈕的label文本改爲“X”。這時候,你會發現上面的按鈕,不能再匹配它的文本了:


那自動佈局是怎麼知道使用哪個按鈕的寬度呢?如果你留心一點,你可能注意到上面的按鈕的尺寸不再正確了:


顯然這不是你想要的效果,選擇上面的按鈕並選擇Size to Fit Content從Editor菜單中(或使用快捷鍵 ⌘ =)。現在,文本再次匹配按鈕了,並且因爲Equal Widths約束的原因,黃色按鈕也改變了尺寸。

運行app並且單擊按鈕,這個按鈕現在總是一樣寬了,不管哪一個label更大:


當然,如果label都非常短,按鈕都會等寬收縮。除非有其他約束阻止了,否則按鈕尺寸將會精確的匹配它們的標題,不多,不少。我們怎麼稱呼這種情況?對的,就是固有內容大小。


固有內容大小

在有自動佈局之前,你總是需要告訴按鈕和其他的控件它們應該有多大,然後設置它們的frame或bounds,再或者在Interface Builder裏恢復它們的尺寸。但是現在情況改變了。大多數控件完全可以根據內容來決定它們到底需要多寬。標籤可以知道自己有多寬多高,因爲它知道文本的長度和文字的大小。按鈕也一樣,可以根據文本,背景圖和內邊距等來決定大小。同樣的,這對於segmented controls, progress bars和其他大多數的控件也都是有效的,不過可能其中有一些控件只能預判高度而不能知道它的寬度。這種特性被叫做固有內容大小,它對於自動佈局是一個很重要的概念。你已經見過button的action了。自動佈局會詢問你的控件需要多大,並且會依據那些信息在屏幕上把控件畫出來。通常情況下,你都會使用固有內容大小,但也有一些特殊情況你不想用它。那你就可以給控件設置一個準確的Width和Height約束來阻止固有尺寸大小的使用。設想一下,你在UIImageView上設置了一張圖片,如果圖片的大小超出了屏幕,會發生什麼?你可以給這個image view一個固定的寬高和縮放比例,除非你想重定義圖片的尺寸。


 如果你給按鈕一個固定寬度的約束會發生什麼?按鈕會計算自己的尺寸,但是如果你給了一個固定的尺寸,它就不會再計算。選擇靠上的按鈕並且選擇Pin\Widthfrom菜單。給按鈕添加一個固定寬度(在按鈕下方出現一個完整的T字架):


因爲這種類型的約束只會對按鈕本身有效,對它的父視圖無效,所以在Outline文檔中,它將會列在按鈕對象下面,你可以固定這個按鈕的寬度爲46點。

你不能簡單的拉伸按鈕來調整大小。如果你這麼做了,你最後只會得到一個橘黃色的虛線框。一定要記住,在Xcode5中不會自動更新約束(不像Xcode4)。因此,如果你改變了按鈕的尺寸和位置,他會提示你需要讓約束再次匹配。這是僅簡單的改變約束的一種替代方法。

選擇Width約束並進入Attributes inspector選項卡。把按鈕的寬度約束改爲80點:


運行app並點擊按鈕,發生了什麼?按鈕的文本改變了,但是它被刪除了一部分,這是因爲在按鈕中沒有足夠的空間:

因爲靠上的按鈕有一個fixed-width約束,並且所有的按鈕都要求一樣的尺寸,所有它們就永遠不會收縮或者擴展。

注意:你可能在設計中不想給按鈕設置Width約束,那最好的辦法就是讓按鈕使用固有尺寸,但是如果你遇到你想在app運行時改變控件的尺寸,但是卻不能的情況。你就應該仔細檢查一下是不是有fixed Width尺寸約束在作祟。

Play around with this stuff for a bit to get the hang of pinning and aligning views. Get a feel for it, (這句翻譯不了,自行解決~)不是一切都能立刻就顯而易見的,你只要記住必須有足夠的約束自動佈局才能預計算所有視圖的位置和尺寸。


畫廊例子

你現在可能會想約束到底是什麼,你要怎麼樣才能通過建立不同視圖之間的關係,從而構建起自己的佈局。在下面一節中,你將看到怎麼使用自動佈局和約束來創建一個符合真實世界場景的佈局。

讓我們假裝你想做一個瀏覽你圖片的程序。它在橫屏和豎屏中看起來就像下面這樣:


屏幕被分成了4塊,每一塊都有一個image view和label。我們要怎麼樣才能達到這種效果呢?

讓我們開始創作這個app吧。首先用Single View Application模板創建一個新的iphone項目,名字叫“Gallery”。

打開Main.storyboard。從Object Library中拖拽View到視圖上。把尺寸設置爲160點和284點,並且把它的背景色變爲白色以外的其他顏色(例如:綠色):


注意:拖拽UIView到故事板上有兩個原因:a)你要使用它來包含其他的控件視圖,這些控件可以幫助你組織起場景的內容;b)它也作爲一個自定義view或控制器的佔位符,你可以設置它的Class屬性,將它變爲你自定義的UIView/UIControl的類(即繼承UIView/UICtontrol的類)。

讓我們給這些view一點約束。你已經看過加約束的兩種方式:用Editor\Pin和Align菜單,還有按住ctrl在兩個view之間拖拽。這裏告訴你第三種添加約束的方法。在Interface Builder窗口的底部有一行這樣的按鈕: 


橢圓包圍的四個按鈕就是用於自動佈局的。從左到右分別是:對其(Align),固定(Pin),解決自動佈局問題(Resolve Auto Layout Issues)和重定義尺寸(Resizing Behavior)。前三個按鈕魚Editor菜單中的對應項有一致的功能。Resizing Behavior按鈕允許你在重新設置view的尺寸的時候,改變已經添加的約束。

選擇綠色的view並且點擊Pin按鈕。會彈出一系列你可以添加的約束:


 

頂部的Spacing to nearest neighbor是最經常用到的。點擊者4個T字架,它們就會變成實體的紅色:


這樣就會給綠色的view和它的父視圖的四邊創建四個新約束。實際上的間距值因人而異,這要看你view的位置。(你不需要改變這些值來匹配我的圖)。點擊 Click Add 4 Constraints完成約束的添加

現在你的故事板看起來應該這樣:


視圖需要這4個約束來保證它的位置。UIView不像button或者label,它沒有固有內容大小。你必須有足夠的約束來保證每個view的位置和尺寸,view總是需要約束來告訴它,它需要多大。

你可能會奇怪,尺寸的約束在哪?在這個案例中,view的尺寸約束是隱含在父視圖的尺寸中。佈局中有兩個Horizontal Spaces約束和兩個Vertical Spaces約束,這樣就能保證固定的長度。你在Outline文檔中可以看到他們:


綠色view的寬是由這個公式計算出來的:“父視圖的寬-(98+62)”,它的高是:“父視圖的高-(65+199)”。這樣,間距就被固定了,你的view除了重新定義尺寸之外別無他法。(你的值可能會有所不同,它依據於你的view放的位置)。

當你旋轉這個app的時候,父視圖的規格就會從320X568變爲568X320。公式就會重新計算寬和高,這時候你會得到新的綠色view的尺寸(408X56)。

你可以旋轉你自己的試試,你也可以在Interface Builder直觀的模擬它們。打開Assistant editor(按Xcode工具欄中看起來像 外星人/男管家 的那個按鈕)並且在彈出的菜單中選擇Preview:


單擊底部的箭頭按鈕可以改變界面的方向。這樣就能立刻給你一個橫屏的故事板佈局預覽。綠色view會重定義尺寸以滿足橫向間距和縱向間距的約束。

你可以留下這個預覽窗口,當你設計UI時,它會自動改變。你也可以在3.5吋和4吋屏之間切換。

注意:你可能會奇怪爲什麼頂部的約束沒有頂到屏幕的最上面:


而是停在了狀態欄處。這是因爲在ios7中,狀態欄總是繪製在view控制器的頂部 - 而不再是一個單獨的bar。這樣做有什麼用?當你創建約束時,它無法確切的依附在屏幕頂部,但是頂部會有一個看不見的行,它叫做 Top Layout Guide。

在一個正常的view控制器中,這個guide距頂部有20點,至少在狀態欄不隱藏的時候是這樣的。在導航控制器中,它位於導航欄下面。因爲導航欄在視圖中有不一樣的高度, Top Layout Guide會在設備旋轉時隨着導航欄移動。這樣就很容易的得到view和導航欄的相對位置。同樣的,底部也有Bottom Layout Guide,用來放置標籤欄和工具欄。

有時候,可能你不想在設備旋轉時重設你的UIView的尺寸,因此你可以給這個view一個固定的寬高約束。現在,讓我們來試試。選擇綠色的view並點擊Pin按鈕;在彈出窗口中給width和height打上勾。


點擊Add 2 Constraints來完成約束的添加。現在你給視圖添加了兩個新約束,一個是寬度160點的約束,一個是高度284點的約束:


因爲寬和高僅應用在當前視圖,所以它們在Outline文檔中位於自己的view下面。一般來說,約束都表達了兩個不同view之間的某種關係 - 例如:橫向間距和縱向間距(Horizontal and Vertical Space)約束就是綠色視圖與它父視圖的關係 - 你也可以認爲寬度和高度約束是綠色視圖和它自己的約束。

運行app,噢,看起來多棒啊。現在把你的屏幕橫置,哎喲!沒有像你想的那樣 - 視圖的尺寸再次該變 - 而是報錯了,Xcode給出了一堆錯誤的信息:


Gallery[39367:a0b] Unable to simultaneously satisfy constraints.
        Probably at least one of the constraints in the following list is one you don't want. Try this: 
     (1) look at each constraint and try to figure out which you don't expect; (2) find the code that 
     added the unwanted constraint or constraints and fix it. (Note: If you're seeing 
     NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for 
     the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0xc1a1e80 V:[UIView:0xc1a2b10(284)]>",
    "<NSLayoutConstraint:0xc1a36c0 V:[_UILayoutGuide:0xc1a2d20]-(65)-[UIView:0xc1a2b10]>",
    "<NSLayoutConstraint:0xc1a36f0 V:[UIView:0xc1a2b10]-(199)-[_UILayoutGuide:0xc1a3230]>",
    "<_UILayoutSupportConstraint:0xc15dbd0 V:[_UILayoutGuide:0xc1a2d20(20)]>",
    "<_UILayoutSupportConstraint:0xc1a1510 V:|-(0)-[_UILayoutGuide:0xc1a2d20]   (Names: '|':UIView:0xc1a2930 )>",
    "<_UILayoutSupportConstraint:0xc1a3720 V:[_UILayoutGuide:0xc1a3230(0)]>",
    "<_UILayoutSupportConstraint:0xc1a30e0 _UILayoutGuide:0xc1a3230.bottom == UIView:0xc1a2930.bottom>",
    "<NSAutoresizingMaskLayoutConstraint:0x8c6c6a0 h=--& v=--& H:[UIView:0xc1a2930(320)]>"
)
 
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0xc1a1e80 V:[UIView:0xc1a2b10(284)]>
 
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
 
. . .

你還記得我說過“必須有足夠的約束,自動佈局才能計算所有視圖的位置和尺寸”麼?好吧,在這個例子中,約束又太多了。當你看到“不能同時滿足所有約束(Unable to simultaneously satisfy constraints)”的錯誤的時候,那就意味着你的約束之間有衝突。

讓我們再看一下哪些約束:


綠色視圖上設置了6個約束,4個間距約束(1-4)和你剛剛設置的寬高約束(5和6)。衝突在哪呢?

在豎屏的時候不應該有問題,因爲在數學上是滿足的。父視圖的寬是320點。如果你添加了Horizontal Space和Width約束,你應該保證它們的總長度小於等於320點。這是我計算視圖位置的方式:98+160+62 = 320。同樣的,所有豎向約束相加以後就應該是568點。

但是當你旋轉設備爲橫屏時,窗口(指的父視圖)就變成了568點寬。這意味着98+160+62+? = 568。這裏額外多了248點,無法滿足方程式,所以自動佈局不知道從哪裏來得到這248點。同樣的,對於豎向約束也是一樣。

解決衝突的辦法有兩個:第一是保持view的寬度固定,但外邊距必須可變。第二是保持外邊距固定,但寬度必須可變。你不能同時固定它們。你要去掉這些約束中的其中一個。在上面的例子中,若你想要在橫屏豎屏中都擁有同樣的寬,那Horizontal Space就必須去掉。

移除右邊的橫向間距和下邊豎向間距。故事板上會顯示成這樣:


好了,現在view就有一個正確約束來預判它的尺寸與位置了 - 不多,不少。運行app來查證一下錯誤信息是否已經沒有了,然後這個view在旋轉以後是否能保持寬度。

注意:儘管Interface Builder可以很好的警告你有無效的約束,但它不是神。它只會警告你:噢,你的約束不夠。但是如果你約束太多了,它就無法檢測出來了。不過至少在出錯的時候,自動佈局還是會給出一個詳細的錯誤信息。如果你想要學習更多關於如何分析錯誤信息和診斷佈局問題,那你可以看 iOS 6 by Tutorials中的“Intermediate Auto Layout”。

豎屏繪製

拖拽一個label到綠色的view上。注意看,現在參考線出現在了綠色view的內部,因爲它是label的父視圖。


調整label的位置到參考線的下邊距,橫向居中的地方。給label下面添加與綠色view之間的間距約束,有20點的距離。快速的方式是使用Pin按鈕,僅選擇下面的T字架:


現在給label添加橫向居中的約束。你已經試過Editor\Align菜單了,但你也可以使用自動佈局菜單的Align按鈕。選擇label並點擊Align按鈕,得到一個彈出窗:


在Horizontal Center前打上勾然後點擊Add 1 Constraint。這時候,故事板應該是這個樣子的:


注意這兩個新的橫向和縱向間距約束是位於綠色view自己的約束列表裏,而不是在主視圖裏。

拖拽一個新的Image View對象到故事板上,讓你的佈局看起來像這樣::


這個圖片視圖固定了上,左,右邊緣在父視圖上,但下部以標準的8點間距連接在了label的頂部。如果你不確定要怎麼做,那就跟着下面的步驟走:

1. 拖拽image view到綠色視圖中,現在不用擔心它的尺寸和位置:


2. 選中image view,按Pin按鈕選擇下面的選項:


上,左,右的T字架設置爲20點,但是下面的設置爲8點。重點:對於Update Frames,你應該選擇Items of New Constraints。如果你左邊距已經默認滿足要求,故事板看起來就是這樣的:


上面這個約束是你選擇了一個不一樣的frame。如果你選擇了Items of New Constraints, Interface Builder將自動的調整畫面來添加約束,一切看起來都很棒:


當然,如果你選錯了frame,你也可以使用Resolve Auto Layout Issues button來修復它:


下載這個教程資源並解壓這個文件。你就會找到一個圖片文件夾 - 添加這個文件夾到你的項目。設置Ray.png作爲這個image view的圖片,改變image view的模式爲Aspect Fit並且設置它的背景色爲白色。把label的文本改爲“Ray”。

你的佈局應該是這樣的:


你可能注意到綠色view內部的約束突然變成了橘黃色。這發生在你給image view設置圖片的時候。你的佈局爲什麼突然無效了?幸運的是你可以帶着你的猜想來讓Xcode告訴你爲什麼錯處了。

點擊這個這個紅色的靶子,它位於View控制器的Outline文檔中:


你會看到一個Content Priority Ambiguity(內容優先級歧義)的錯誤。這意味着:如果image view和label都沒有固定的高度,那自動佈局系統就不知道應該怎麼分配大小。(Interface Builder似乎會忽略已經設置過的固定高度約束)

讓我們把綠色的view的高度變爲100點試試。自動佈局是怎麼將這100點分配給view內部的label和image view的?是label保持尺寸而image view變成100點高了麼?還是label變高了而image view保持尺寸?還是他們各自被分配了50點,又或者劃分成25/75,40/60,又或者其它的什麼結合?

如果你不解決這個問題,那麼自動佈局系統就會去猜測,就可能導致結果莫名其妙。

恰當的解決辦法是改變label的“Content Compression Resistance Priority”。你將從隨後內容中學習到更多。現在,打開label的Size inspector,設置Content Compression Resistance Priority的vertical爲751。這樣就使它的優先級高於image view。然後繼續設置Content Hugging Priority爲252。


這時候T字架就會再次變成藍色,自動佈局系統的警告也會消失。

添加到另一個頂端

拖拽綠色的view到主視圖的左上角。回憶一下之前做過的,綠色的view已經有Horizontal Space和Vertical Space約束來控制它在父視圖中的位置了。現在,這些約束依然存在,它們導致了視圖的frame無法對齊參考線。


爲了修正它,使用Resolve Auto Layout Issues按鈕並選擇Update Constraints。之前你使用的Update Frames,用來移動和重定義view的尺寸來匹配你的約束。現在這剛好是一個相反的操作:你會用你的約束來匹配view的frame。

你可能注意到在頂部的Vertical Space現在是負的。這種情況是因爲約束鏈接到了Top Layout Guide(頂部參考線)。不要問爲什麼,沒有規定說約束值不能是負的,你可以就像這樣留下它。(如果你看着礙眼,那就刪掉 “Vertical Space (-20)”約束,然後把view重新綁定到窗口的頂部)

Horizontal Space現在爲0,表現爲一條緊貼窗口左邊緣的粗藍線。儘管view已經位於角落裏了,但是它任然需要約束來固定住它:


選擇綠色的視圖按下⌘D來複制它。移動複製的視圖到右上角:


注意T字架現在是橘黃色的。當你複製它的時候,它已經上丟失了X,Y座標的約束。爲了修正它,固定view到窗口的右、上邊緣。

再複製兩次,分別把複製的視圖放在坐下角落和右下角落。然後再把他們固定在他們該在地方。

改變後的場景如下:


 

哈哈~他們看起來好像都是程序員 :-)

運行app,在豎屏上很棒,但是橫屏上可能不是那麼好:


本來應該很棒的戰士卻變糟了:你給4個色彩鮮豔的容器view設定了固定的寬和高,因此它們就總是保證這樣的尺寸,而不管它們的父視圖的尺寸如何。

從這4個view下,選擇Width (160)和Height (284)的約束並刪除它們(在Outline文檔中很容易完成)。如果你現在運行app,你會看到:


注意:如果你很奇怪爲什麼有的view變得比其他的大了,那我告訴你,這是固有內容尺寸的原因。圖片的尺寸決定了image view有多大。文本的尺寸決定了label有多大。再加上四邊的20點外邊距,所有的尺寸加起來決定了每個視圖的尺寸。

這看起來很像你的上一部分解決的問題,因此你要向前思考,你可能回憶起來了,你要讓每個view的寬和高都相等。

選擇這4個view。Outline大綱裏很容易完成;按住⌘點擊這4個view。你就可以添加這個約束。在彈出窗中,給Equal Widths和Equal Heights打上勾,然後點擊Add 6 Constraints。


再次運行這個app並且旋轉設備。噢...還是不咋地:


所有的view都有同樣的高,並且他們也有同樣的寬,你的約束是對的。但這個寬和高可能不是你想要的。

光告訴自動佈局系統必須有同樣的尺寸是不夠的。因爲自動佈局系統不知道這4個view是互相連接的。它們的邊貼邊的設計,但在它們之間沒有這樣的約束。自動佈局就不知道它需要在“Ray”個“Matthijs”之間劃分窗口。

如果自動佈局系統自己不能完成,那你就要告訴它。


選擇Ray和Matthijs的視圖,從編輯菜單選擇Pin\Horizontal Spacing。因爲view之間是邊貼邊的,那在它們之間就要加上尺寸爲0的Horizontal Space約束。好了,現在有足夠的約束讓自動佈局系統瞭解兩個view之間的關係了。再給Ray和Dennis Ritchie視圖之間使用Editor\Pin\Vertical Spacing。

再運行app,現在看起來就像這樣:


注意:Interface Builder任然會抱怨view的位置不對。我不確信爲什麼會發生這種情況,可能是Xcode有bug吧。如果這些警告信息讓你不爽,那就選擇主視圖(或view控制器)並且從Resolve Auto Layout Issues菜單選擇Update All Frames in View Controller。不過你的app運行時這種改變不會起作用,但至少讓Xcode看起來很舒心。

image view上有一點要注意:它們可能會延伸了,因爲你沒有給它們一個固定尺寸。你可能不知道,這是有意這樣做的。image view不能適配橫屏的模式。如果你想保持image view的原始寬高比,那很不幸。在Interface Builder中你是無法得到下面這樣的效果的:






Demo代碼:

不幸的是,Interface Builder不能正確的提供用約束保持view的原樣寬高比。如果要這麼做,你就需要自己編程。你可以在iOS 6 by Tutorials的“Intermediate Auto Layout”中學習如何操作。

我該去哪繼續學習?

如果你全都看完做完了,祝賀你 - 你現在知道自動佈局是什麼了,並且有了一定的實踐基礎!但你任然有許多需要學習...

這個教程的第一部分你可以從iOS 6 by Tutorials一書中的自動佈局章節閱讀到。第二部分會教你如何使用自動佈局來創建更多“真實世界”的場景佈局,你可以從Interface Builder學習一切你想知道的自動佈局的知識。

但是就像其他的視覺設計工具一樣,Interface Builder也有自己的侷限性。有時,它只能通過NSLayoutConstraint對象來從代碼實現。IOS 6的教程有一個章節包含了這個主題所有的內容,Intermediate Auto Layout。因此,如果你想知道自動佈局的後半部分,請買這本書吧!(翻譯到最後,發現居然帶廣告,爲了尊重原作者,還是給出連接吧~~~)


發佈了25 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章