(翻譯)開始iOS 7中自動布局教程(二),ios布局
這篇教程的前半部分被翻譯出來很久了,我也是通過這個教程學會的IOS自動布局。但是後半部分(即本篇)一直未有翻譯,正好最近跳坑翻譯,就尋來這篇教程,進行翻譯。前半部分已經轉載至本部落格,後半部分即本篇。學習IOS自動布局的朋友可以看看。自動布局很強大。
這篇教程絕對的最好的學習IOS自動布局的文章,沒有之一
原文地址:Beginning Auto Layout Tutorial in iOS 7: Part 2
本文如下:
請注意: 團隊成員Matthijs Hollemans (IOS學徒系列的作者) 已經將這篇教程移植到iOS 7 feast. 我們希望您喜歡!
在開始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可以很好的警告你有無效的約束,但它不是神。它只會警告你:噢,你的約束不夠。但是如果你約束太多了,它就無法檢測出來了。不過至少在出錯的時候,自動布局還是會給出一個詳細的錯誤資訊。如果你想要學習