你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> Swift語言Auto Layout入門教程:下篇

Swift語言Auto Layout入門教程:下篇

編輯:IOS開發基礎

接上篇:Swift語言Auto Layout入門教程:上篇

原文:Beginning Auto Layout Tutorial in Swift: Part 2/2   譯者:@TurtleFromMars

autolayout-square-250x250.png

開始用自動布局約束的方式思考吧!

更新記錄:該教程由Brad Johnson更新Swift和iOS 8內容,原文第一版作者為教程編纂組的Matthijs Hollemans。

在教程上篇我們了解到,以前的“彈簧支撐桿”模型難以解決所有界面布局問題。自動布局就是用來解決這些問題的,不過由於這項技術比較強大,使用起來也對技巧性有更高要求。

幸虧Xcode 5大幅優化自動布局。如果你以前在Xcode 4嘗試自動布局半途而廢,請您再給Xcode 6一次機會。

這一篇我們會繼續學習約束的性質和應用,結束自動布局的入門教程,

一點題外話:運行時

本教程開始構建的是如下圖的簡單App:

402.jpg

有兩個不同背景色的按鈕,這樣我們可以清晰辨認具體范圍。按鈕之間有若干約束,各位可以從上篇構建的App繼續,在面板上刪掉其余兩個按鈕就可以了。

如果沒有上篇的項目,請用Single View Application新建一個Swift iPhone應用,向場景內拖入兩個按鈕,設置背景色,用Editor/Pin菜單在兩者之間創建Vertical Spacing約束(40點),給下面的按鈕創建Bottom Space to Superview約束(20點),用Editor/Align菜單使黃色按鈕在容器內水平居中,然後讓兩個按鈕左對齊。

在Interface Builder裡設置約束自然得心應手,現在我們來看看約束如何在運行時生效。在ViewController.swift中添加如下方法:

@IBAction func buttonTapped(sender: UIButton) {
  if sender.titleForState(.Normal) == "X" {
    sender.setTitle("A very long title for this button", forState: .Normal)
  } else {
    sender.setTitle("X", forState: .Normal)
  }
}

觸發該事件的按鈕會在長短文字間切換。在Interface Builder中按住control分別從兩個按鈕拖到視圖控制器上,然後在彈出的選單中選擇buttonTapped:,連接action方法。

運行App,點擊按鈕,觀察按鈕行為。切換橫豎屏模式,再次試驗。

001.png

無論哪個長哪個短,布局始終滿足設置的約束:

  • 下面的按鈕在窗口上始終水平居中。

  • 下面的按鈕始終距離窗口下邊緣20點。

  • 上面的按鈕始終距離下面按鈕40點,兩者左對齊。

這是該用戶界面完整的規范說明。

試試看,刪除Leading Alignment約束(在文檔大綱中選定後按delete鍵),然後同時選中兩個按鈕,在Editor/Align中選擇Right Edges,運行App,觀察區別。

再試試換成Align/Horizontal Centers,讓兩個按鈕水平中心對齊。運行App,觀察點擊按鈕後的反應。(修改約束時如果碰到橙色虛線框,記得利用Editor/Resolve Auto Layout Issues菜單的適當操作更新按鈕框架。)

修復寬度

Pin菜單有個選項是Widths Equally,在兩個視圖間設置後會使兩者等寬(取值為其中最寬的一個)。我們來玩一玩吧。

選中兩個按鈕,由Editor/Pin/Widths Equally添加新約束:

002.jpg

上篇中我們見過這類約束,同樣以“工字梁”表示,不過中間有個等號圓圈。

雖然用兩條線表示,但文檔大綱中顯示為一個Equal Widths約束:

003.png

修改其中一個按鈕的文本,另一個的寬度也會隨之改變。把下面按鈕的文本改成"X",按鈕會縮短,這時你會注意到上面的按鈕已經無法容納原來的文字了:

003.jpg

自動布局怎麼決定以誰的寬度為准呢?觀察細致的話,你會發現上面按鈕出現了視圖錯位提示:

004.jpg

這顯然不是我們想要的效果,我們選中上面的按鈕,然後在Editor菜單選擇Size to Fit Content(或按command-=)。現在文字已經能在按鈕中完整顯示了,更確切地說是按鈕尺寸適應文本,然後等寬約束使得黃色按鈕也隨之變寬。

運行App,點擊按鈕,現在無論哪個按鈕更長,兩者都會保持等寬:

005.png

當然文字都很短的時候兩個按鈕也會同時縮短到相同尺寸,畢竟按鈕會為完全適應內容大小而自動調節尺寸,除非另有約束規定。這叫什麼來著?對,固有內容尺寸(intrinsic content size)。

在自動布局引入之前,開發者必須指定按鈕和其他控件的具體大小,無論是設置frame或bounds屬性還是在Interface Builder裡調整尺寸,但有一個事實是大多數控件都完全能根據內容確定自己需要多少空間。

文本標簽知道其中文本的長度和字體大小,故能確定自己的寬高。同理,按鈕可以靠文字、背景圖片和內邊距綜合推算自身尺寸。標簽切換控件(segmented control),進度條等其他控件大多如此,不過有些控件可能只會推算自己的高度,寬度還另需設定。

這就是固有內容尺寸,自動布局中的重要概念之一。我們已經在按鈕上看到具體效果了,自動布局會自動詢問各個控件需要多大空間,然後根據這些信息進行布局。

利用固有內容尺寸再也通常不過,但在另有需求的個別情況下,你可以在控件上設置明確寬度約束或高度約束。

假如,你想在UIImageView上設一張圖片,如果圖片尺寸過大,一般會固定UIImageView的寬高,然後縮放圖片,除非希望UIImageView被圖片撐大。

那要是有個按鈕存在固定寬度約束會怎樣呢?按鈕確實可以計算自身大小,但明確指定寬度後固有內容尺寸無效。選擇上面的按鈕,然後選擇Pin/Width,按鈕下方又多了一條橫梁。

006.jpg

這類約束只對控件本身有效,對父視圖沒有影響,所以在文檔大綱中歸屬於按鈕對象。這裡我們把按鈕寬度定為46點:

007.png

如果你直接靠拖鼠標來拉寬按鈕的話,橙色方框會接踵而至。Xcode 6不像Xcode 4那樣會自動更新約束,所以在修改框架的時候還是要親手調教約束,或者干脆直接修改約束。

選擇寬度約束,在屬性檢查器裡設Constant為80,讓按鈕變寬:

008.jpg

運行App,點擊按鈕看看如何。按鈕文本確實變長了,但由於空間不足而被截斷:

009.jpg

因為上面的按鈕存在固定寬度約束,而兩個按鈕之間又存在等寬約束,按鈕的尺寸始終不變。

注:不要隨便給按鈕設置寬度約束,默認的固有內容尺寸就是最佳選擇。如果布局時發現應該改變尺寸的控件沒有變化,請仔細檢查是否存在誤設的固定約束。

繼續練練手,邊做邊感受,慢慢掌握固定和對齊視圖的做法。記住,要確定所有視圖的位置和尺寸,自動布局需要足夠的約束。

010.jpg

畫廊式布局示例

現在我們已經大致了解約束是什麼,還有如何靠指定視圖空間關系來構建布局。接下來你會看到如何利用自動布局和約束來構建一個符合實際應用情景的布局。

假設你想做一個App,在裡面陳列你最喜歡的幾位程序猿。橫豎屏效果圖如下:

011.jpg

屏幕被四等分,每塊都包含一個圖像視圖和一個文本標簽,要怎麼做呢?

我們這就開始。使用Single View Application模板新建一個Swift iPhone項目,命名為“Gallery”。

打開Main.storyboard,禁用尺寸歸類,因為對本教程而言Storyboard編輯器中顯示iPhone比例的布局更好些。選中視圖控制器,在屬性檢查器中選擇Size為iPhone 4 inch。從對象庫中拖入一個普通View對象,調整大小為寬160點、高284點,選擇相對顯眼的背景色(比如綠色):

012.jpg

選用普通UIView的主要原因有兩個:一是我們要把它當作其他視圖的容器,這樣方便組織場景內容;二是將其作為自定義視圖控件的占位符(placeholder),之後可以把它的Class屬性設為自己的UIView或UIControl子類。

我們來為視圖設定約束,之前設定約束的方法有兩種:Editor/Pin和Align菜單,還有在視圖間按住control進行拖拽。這裡我們介紹第三種方法,在Interface Builder窗口底部有一排按鈕:

013.jpg

這四個按鈕都與自動布局相關。從左到右依次為:對齊(Align)、固定(Pin)、化解自動布局問題(Resolve Auto Layout Issues)、尺寸變動行為(Resizing Behavior)。前三個按鈕功能和Editor菜單中的對應選項一致,第四個按鈕可以指定重設視圖尺寸時約束如何變化。

選中綠色視圖,點擊Pin按鈕,彈出選單中有各種約束可供添加:

014.jpg

最上的Spacing to nearest neighbor(近鄰間隔)是最常用的區域。取消選擇Constrain to margins(約束以留邊為准)然後點擊那四條“工字梁”使其變紅:

015.jpg

“留邊式約束”是Xcode 6和iOS 8的一項新特性。還記得前面拖動視圖時出現的輔助線嗎?以前創建的間距約束都相對於父視圖的真正邊緣,而現在你可以創建相對於父視圖“留邊”的間距約束。一個視圖可以定義自己的留邊尺寸,使得布局更加靈活。不過本教程中依然使用傳統的邊緣約束。

綠色視圖和父視圖間新建了四個約束,分別對應上下左右,間距的具體值取決於視圖放置位置,不必與本示例相同。點擊Add 4 Constraints完成。

現在的Storyboard如圖所示:

016.jpg

你可能會問為什麼視圖頂部的約束沒有直通屏幕的上邊緣:

017.jpg

只通到了狀態欄。在iOS 7中狀態欄始終繪制於視圖控制器上方,再也不作為單獨的欄出現。這意味著什麼呢?當你創建相對於屏幕頂端的約束的時候,實際上是相對於一條看不見的線而言的,這條線叫做Top Layout Guide(頂端布局指示線)。

在常規視圖控制器中,至少在狀態欄沒有隱藏的時候,這條指示線位於屏幕上邊緣下方20點處。而在導航控制器中,指示線位於導航欄下方。因為導航欄在橫豎屏模式下的高度不同,隨著設備的旋轉,頂端布局指示線也會移動。如此一來相對導航欄放置視圖更容易了,還有一條Bottom Layout Guide(底端布局指示線)對應分頁欄和工具欄。

該視圖需要四條約束確定位置。與按鈕或文本標簽不同,一個普通的UIView視圖不存在固有內容尺寸的概念,只能靠足夠的約束來確定位置和大小,所以該視圖需要指定尺寸的約束。

尺寸約束在哪裡?這裡我們可以根據父視圖尺寸推斷該視圖的大小。布局中存在兩條水平間隔約束和兩條垂直間隔約束,四條約束均為定長,可以在文檔大綱中看到:

018.png

綠色視圖的寬度公式為“父視圖寬度 - ( 54 + 74 )”,高度公式為“父視圖高度 - ( 50 + 214 )”。而間隔約束均為定長,所以視圖大小已經確定下來了。(此處具體數值可能不同。)

切換橫豎屏時,父視圖尺寸變化(寬高交換),公式也隨之改變。

這一點可以靠運行App切換橫屏模式驗證,也可以直接在Interface Builder中模擬。點擊Editor/Simulated Screen,選擇一個iPhone橫屏,你會看到App的橫屏效果:

019.jpg

你可能並不希望UIView在設備旋轉時調整大小,我們這就利用約束為視圖指定明確寬高。選中綠色視圖,點擊Pin按鈕,在彈出的選單中令Width和Height處於被選狀態。

020.jpg

點擊Add 2 Constraints完成。現在向視圖添加了兩條約束,一個是160點寬度約束,另一個是284點高度約束:

021.jpg

寬高約束因為只對該視圖有效,在文檔大綱中歸屬於綠色視圖本身。通常約束用來表達兩個不同視圖之間的幾何關系,比如水平間隔約束和垂直間隔約束存在於綠色視圖和父視圖之間,但你可以將寬度約束和高度約束視為綠色視圖和自身之間的關系。

運行App,哦,豎屏看起來不錯。切到橫屏,哎呀!不只是看起來不對勁(視圖尺寸又變了),Xcode調試面板中還出現了蛋疼的報錯信息:

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)

(

"

)

Will attempt to recover by breaking constraint....

還記得嗎,為使自動布局計算所有視圖的位置和大小,我們需要設定足夠的約束。這次的問題恰恰是“約束過多”,報錯信息“Unable to simultaneously satisfy constraints(無法同時滿足多條約束)”表示存在約束沖突。

我們再來審視一下這些約束:

022.jpg

綠色視圖共設有六條約束,前面四條間隔約束,還有新增的寬度約束和高度約束。哪裡有沖突呢?

豎屏模式下沒有問題,因為計算結果正好合適。水平間距和寬度約束加起來正好是父視圖寬度,垂直方向上也一樣。

但設備轉成橫屏時,結果出現分歧,自動布局不知如何是好。

對於此處的沖突,我們可以認為視圖寬度固定但邊距必須變化,或者是邊距固定但寬度必須變化。所謂二者不可得兼,捨其一而取其余者也。本例中,我們希望視圖在橫豎屏模式下寬度相同,所以必須捨去水平方向上的間隔約束。

刪除右側的水平間隔約束和下側的垂直間隔約束,Storyboard如圖所示:

023.jpg

現在視圖的約束恰好可以確定其位置大小,不多不少剛剛好。運行App驗證是否存在報錯信息,旋轉前後視圖寬度是否保持一致。

就算Interface Builder盡力提醒布局是否有效,它也不是全能的。缺少約束時會提示,但目前對約束過多的情況束手無策。

不過至少在運行時,自動布局會在必要時給出詳細的報錯信息。為了解如何分析這類報錯信息並診斷布局問題,請參閱iOS 6 教程的“中級自動布局”。

顯示畫像

向綠色視圖中拖入一個Label文本標簽,注意其中出現的輔助線,因為綠色視圖會作為文本標簽的父視圖。

44.jpg

利用輔助線,把文本標簽放在水平居中並緊貼底部留邊的地方。在文本標簽上添加距綠色視圖底邊20點的底端間隔約束,最快的方式是利用Pin按鈕,選擇底部的梁:

45.png

現在添加水平居中約束。之前我們一直在用Editor/Align菜單,你也可以用Align按鈕來搞定。選中文本標簽,點擊Align按鈕,彈出選單:

024.png

選中Horizontal Center in Container,點擊Add 1 Constraint,此時Storyboard應如圖所示:

025.png

注意新加的這兩條約束都列在綠色視圖之下,而不是主視圖。

向Storyboard中拖入一個Image View對象,如圖布局:

026.jpg

該圖像視圖的上、左、右邊緣都相對於父視圖設有固定間距,不過下邊距(相對於文本標簽上邊)為8點標准間距。如果沒有足夠把握,請遵循以下步驟:

1. 不要在意具體大小和位置,把Image View拖入綠色視圖:

027.jpg

2. 選中圖像視圖,點擊Pin按鈕,選擇以下選項:

028.png

上、左、右邊距均設為20點,下邊距設為8點。最重要的一點:對於Update Frames,你應該選擇Items of New Constraints,如果忘記設定(默認為None),Storyboard會變成這個樣子:

029.png

默認選項會導致設定的約束與圖像視圖的實際位置大小不同,但選擇Items of New Constraints的話,Interface Builder會在添加約束時自動調整視圖框架,干淨利落:

030.png

當然,如果出現視圖框架錯位,可以用Resolve Auto Layout Issues按鈕來修復:

031.png

添加圖片

接下來下載教程資源,解壓後將其中的Images文件夾添加到項目中,把Ray.png設為圖像視圖的圖片,將其模式修改為Aspect Fit(局部適應,即把整張圖片按比例縮放,在視圖中呈現全貌),設背景色為白色。將標簽的文本修改為“Ray”。

布局如圖所示:

032.jpg

注意在設定圖片時,綠色視圖內的約束變為橙色。為什麼突然就布局無效了呢?在Xcode中查看錯誤之前我們可以先猜一猜。

點擊文檔大綱中View Controller Scene右側的紅色箭頭圖標,檢查問題:

033.png

報錯為Content Priority Ambiguity(內容優先級不明確),這個叫法有些晦澀,表達的意思是:如果圖像視圖和文本標簽都沒有固定高度,那麼當綠色視圖高度變化時,自動布局不清楚各個子視圖要分別縮放多少,也就是說,不清楚按什麼比例分配縮放空間。(目前的Interface Builder似乎會忽視綠色視圖上存在的固定高度約束。)

我們再來具體地講解一遍,假設某時刻綠色視圖高度增加了100點,那麼自動布局會如何在文本標簽和圖像視圖兩者間分配這新加的100點高度呢?是文本標簽原封不動,圖像視圖增高100點?還是圖像視圖不動,文本標簽增高?是兩者分別增高50點,還是三七開、四六開?還是其他可行方案?

如果不指明,自動布局只能自行揣度,結果可能很難預測。

正確的使用姿勢是修改文本標簽的Content Compression Resistance Priority(內容抗壓優先級),顧名思義,該值決定視圖的抗壓縮屬性,抗壓優先級高意味著視圖更傾向於保持原尺寸,降低被縮小的可能性。

與此相對,Content Hugging Priority(內容貼合優先級)決定視圖的抗膨脹屬性。此處的“貼合”可理解為“剛好合適的尺寸”,即視圖邊緣貼合內容,或接近固有內容尺寸。貼合優先級高意味著視圖更傾向於保持原尺寸,降低被擴張的可能性。

在文本標簽的尺寸檢查器中設Content Compression Resistance Priority / Vertical為751,高於圖像視圖的優先級。同時設Content Hugging Priority / Vertical為252。當父視圖尺寸變化時,隨之改變的是圖像視圖,而文本標簽會保持原樣。

034.png

約束再次變藍,自動布局警告一掃而空。

照葫蘆畫瓢

把綠色視圖拖到主視圖的左上角,不過我們之前在上面設置了水平間隔約束和垂直間隔約束,拖動會造成視圖布局錯位。

035.jpg

Resolve Auto Layout Issues按鈕派上用場了,點擊並選擇Update Constraints。之前我們選過的Update Frames的作用是依照約束規定調整視圖框架,而這次正好相反:依照視圖框架更新約束信息。

注意頂端垂直間距現在變成了負數,這是因為該約束的參照物是頂端布局指示線。約束值出現負數在道理上也講得通,所以此處無需干預。(如果您有重度強迫症,請刪除那個帶負數的約束,然後參照父視圖頂端重新設置約束。)

水平間隔現在為0,由左邊緣的藍線表示。視圖已經安放在左上角,但真正把它定住的還是約束:

036.png

選中綠色視圖,按下command-D克隆視圖,將復件放到右上角:

037.png

注意出現橙色,復制視圖時自然不會保留XY坐標約束。為此,我們要設置約束,把視圖復件固定在右上角。

再復制兩次,分別放到左下角和右下角,依然設置邊緣約束。

頁面設計如下:

038.png

歡迎來到國家動物園程序猿館!(`?ω?′)

運行App,豎屏還不錯,橫屏有問題了:

039.png

顯然,問題在於四個視圖都設定了固定寬高約束,無論父視圖有多大,尺寸始終保持不變。

再文檔大綱中選中並刪除四個視圖的所有固定寬高約束。現在運行App會變成這樣:

040.png

注:這裡四個視圖大小不一致的原因依然是固有內容尺寸,圖片尺寸決定圖像視圖大小,文本尺寸決定文本標簽大小,加上周圍的20點邊距,放到一起決定各個視圖的大小。

有種上篇問題的即視感,還記得當時是如何解決的嗎?對,等寬約束和等高約束。

在文檔大綱中按住command分別選中四個視圖,在Pin按鈕中選中Equal Widths和Equal Heights,點擊Add 6 Constraints,批量添加約束。

041.png

運行App,橫屏,嗯…還是不大對:

042.png

所有視圖確實滿足等寬等高的約束,但具體的寬高數值與預期不符。

也就是所有視圖等寬等高不足以確定實際大小,因為自動布局不清楚四個視圖之間的空間關系。雖然並排放置,但其間不存在約束,自動布局不知道你的意圖是把畫面四等分。

自動布局不明白,我們就要講清楚。

043.png

選中上面的兩個視圖,選擇Editor/Pin/Horizontal Spacing,由於兩個視圖並列排放,該操作會在兩個視圖之間添加一條水平間隔約束,間距為0,同樣在左側兩個視圖之間設置垂直間隔約束,這樣就講清楚了。

運行App,最終效果如下:

044.png

何去何從?

一路過關斬將,終於掌握自動布局的基本使用方法,可喜可賀。當然還有好多東西等著你去學習…

想要深入了解,請購買我們的iOS 8教程集,其中有一部分專門講解尺寸歸類和自適應布局。?

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved