第 5 章
自動佈局介紹
Life is short. Build stuff that matters. – Siqi Chen
建立一個 Hello World App 是不是很有趣呢?在建立一個真實 App 之前,我們在本章中先介紹自動佈局(Auto Layout )。
自動佈局是一個以約束條件為基礎的佈局系統(constraint-based layout system),它讓開發者能夠開發一個自適應 UI,可以依照螢幕的尺寸以及裝置的方向來調整。有些初學者會覺得這個部分很難,而儘量避免去使用它,但是請相信我,當你習慣之後,自動佈局會成為你之後無比仰賴、非常重要的 App 開發工具。
iPhone 在十多年前上市時,只有一種3.5 英吋螢幕尺寸,之後出現 4 英吋的 iPhone。在 2014 年,Apple 導入了iPhone 6 以及 6 Plus,現在iPhone 有了不同的螢幕尺寸,包含 3.5 英吋、4 英吋、4.7 英吋、5.5 英吋、5.8 英吋、6.1 英吋與 6.5 英吋顯示器。當你想設計你的 App UI,你必須要應付不同的螢幕尺寸。如果你的 App 準備支援 iPhone 與 iPad(也就是所謂的通用 App),你需要確保這個 App 能夠相容其他的螢幕尺寸,其中包含 7.9 英吋、9.7 英吋、10.5 英吋、11 英吋與 12.9 英吋。
如果沒有使用自動佈局,想要建立一個支援各種螢幕解析度的 App 將會非常困難。這也是為什麼我想要在本書一開始的內容,先教你學會自動佈局,而不是直接寫 App 程式碼的原因。這會花費你一些時間,不過我想要你能夠了解其中的觀念。在本章以及接下來的章節中,我會協助你建立設計「自適應使用者介面」(adaptive user interface )的紮實基礎。
Quick note: 自動佈局並不如開發者所想的困難。當你瞭解這些基礎以及下一章會學習到的堆疊視圖後,你將能夠針對各種形式的 iOS 裝置,使用自動佈局建立出複雜的使用者介面。
為什麼需要自動佈局?
這裡舉個例子,或許你就會對我們為何要使用自動佈局更有概念了。請開啟第 3 章的 HelloWorld 專案,以iPhone SE 或是 iPhone 15 Pro Max 模擬器取代在 iPhone 15 Pro 模擬器中執行程式,其結果會如圖 5.1 所示,除了6.1 吋螢幕以外,在其他的 iPhone 裝置中畫面的按鈕皆沒有置中。
我們來試試其他狀況。
點選「停止」( Stop )按鈕,並選擇 iPhone 12 Pro 模擬器來執行 App。啟動模擬器後,在選單上點選「Device → Rotate Left(或 Rotate Right)」,此時裝置便會轉為橫向(landscape) 模式。另外,你可以按下鍵加上鍵,將裝置往側邊轉向。同樣的,「Hello World」按鈕的位置還是沒有置中。
為什麼會這樣呢?出了什麼問題嗎?
首先,你應該知道iPhone 有不同的螢幕尺寸:
- iPhone SE(舊機型)的螢幕大小在直立(portrait)模式的情況下,水平方向是 320點( 640 像素),垂直方向為 568 點(1136 像素)。
- iPhone 6/6s/7/8/SE(新機型)的螢幕大小在水平方向是 375點(或 750 像素),垂直方向則是 667 點(或1334 像素)。
- iPhone 6/6s/7/8 Plus的螢幕大小在水平方向是 414點(或 1242 像素),垂直方向則是 736 點(或 2208 像素)。
- iPhone X/XS/11/12 Pro/13 mini 的螢幕大小在水平方向是 375 點(或 1125 像素),垂直方向則是 812 點(或 2436 像素)。
- iPhone XR/11/12的螢幕大小在水平方向是 414點(或 828像素),垂直方向則是 896 點(或 1792 像素)。
- iPhone XS Max/11/12 Pro Max的螢幕大小在水平方向是 414 點(或 1242像素),垂直方向則是 896 點(或2688 像素)。
- iPhone 13/13/14 Pro的螢幕大小在水平方向是 390點(或 1170像素),垂直方向則是 844 點(或 2532 像素)。
- iPhone 13/14 Pro Max 的螢幕大小在水平方向是 428點(或 1284像素),垂直方向則是 926 點(或 2778 像素)。
- iPhone 15 Pro的螢幕大小在水平方向是 393點(或 1179像素),垂直方向則是 852 點(或 2556 像素)。
- iPhone 15 Pro Max 的螢幕大小在水平方向是 430點(或 1290像素),垂直方向則是 932 點(或 2796 像素)。
- iPhone 4s 的螢幕大小則分別為 320 點( 640 像素)以及 480 點( 960 像素)。
為什麼是點(Points ),而不是像素(Pixels)呢?
B時間回到 2007 年,Apple 推出的最原始iPhone,有 3.5 英吋、320×480 像素的畫面解析度的顯示器,即水平方向是 320 像素,垂直方向是 480 像素。Apple 把這個螢幕解析度繼續沿用到 iPhone 3G 與 iPhone 3GS。很明顯的,如果我們在那時候建立一個 App,一個點相對應一個像素。之後 Apple 推出了iPhone 4 搭配視網膜(retina)顯示器。畫面的解析度變成兩倍至 640×960 像素。也就是一個點相對應二個像素。 這個點系統(point system)讓開發者輕鬆不少,不論螢幕解析度如何變化(例如:解析度再變兩倍至1280 ×1920 像素),我們要面對的依然是點與最基本的解析度(也就是 iPhone 4/4s 是320×480 像素, iPhone 5/5s/SE 是 320×568 像素),而這些點與像素間的轉換工作,則由 iOS 處理。
如果沒有使用自動佈局,我們在 Storyboard 所佈局的按鈕位置是固定的,換句話說,我們寫死按鈕的 frame 原點。以我們的範例而言,「Hello World」按鈕的 frame 原點原本設定在(137, 407),你可以在尺寸檢閱器(Size inspector),也就是在屬性檢閱器( Attributes inspector )旁的按鈕,找到這個絕對座標,因此不論你是使用4.7 英吋或 6.7 英吋模擬器,iOS 會將按鈕繪製在指定位置。圖 5.2 顯示了這個按鈕在不同裝置下的frame 原點,這說明了「Hello World」按鈕只能在 iPhone 15 Pro 裝置置中,而在其他 iOS 裝置以及橫向模式中都會偏移的原因。
顯然地,我們希望 App 在所有 iPhone 機種,不管是在橫向模式或直立模式都要看起來一致才是。此即我們需要學習自動佈局的緣故,也是對於我們剛才介紹的畫面佈局問題的答案。
自動佈局和約束條件有關
如前所述,自動佈局是以約束條件(constraints )為基礎的佈局系統。它讓開發者建立自適應 UI,可以因應螢幕尺寸與方向的不同。嗯,聽起來好像不錯,但是什麼是「以約束條件為基礎的佈局」?
讓我以白話來表達好了,再次以「Hello World」按鈕為例,如果你要把它擺在視圖的中間,你會如何描述它的位置呢?也許你會這樣描述:
「不管螢幕解析度與方向為何,按鈕應該是在水平以及垂直方向的中間位置。」
這邊就會得到兩個約束條件:
- 水平置中。
- 垂直置中。
這些約束條件顯示了按鈕在介面中的佈局。
自動佈局是和約束條件有關。雖然我們把約束條件以文字表達,但這些約束條件是以數學形式來呈現。舉例而言,當你要定義一個按鈕的位置,你可能會這樣說:「左側邊緣距離內容視圖的邊緣是 30 點」,這會被轉換為 button.left = (container.left + 30)
。
很幸運地,我們不需要去和公式打交道,你只要知道如何描述這些約束條件,並使用介面建構器來建立它們即可。
好了,自動佈局的理論就談到這裡為止。現在我們來了解如何在介面建構器中定義佈局約束條件,以讓「Hello World」按鈕置中。
在介面建構器中做即時預覽
首先,開啟 HelloWorld 專案(http://www.appcoda.com/resources/swift59/HelloWorld.zip)的Main
。在加入佈局約束條件至使用者介面之前,我們先介紹 Xcode 的一個很方便的功能。
你可以使用模擬器來測試 App UI 在不同螢幕尺寸的呈現狀況。不過,Xcode 在介面建構器中提供了一個設定列(Configuration Bar ),讓使用者能夠即時預覽使用者介面。
介面建構器預設上是以 iPhone 15 Pro 預覽UI。當想要看 App 在其他裝置的顯示狀況,點選裝置按鈕來開啟設定列,然後選取你想測試的 iPhone/ iPad 裝置。你也可以改變裝置的方向來了解 App UI 的變化。圖 5.3 顯示 Hello World App 在 iPhone 15 Pro 的即時預覽( live preview )。
這個設定列是能夠在不同裝置上預覽 UI 的一個很棒的功能,請花點時間來熟悉一下。
使用自動佈局將按鈕置中
我們繼續說明自動佈局(Auto Layout )。Xcode 提供了兩種定義自動佈局約束條件的方式:
- 自動佈局列。
- 鍵加上拖曳。
我們將在本章示範這兩種方法。首先從自動佈局列開始。在介面建構器的編輯器右下角處,你會找到五個按鈕。這些按鈕就是佈局列,你可以使用它們來定義各種型態的佈局約束條件與解決佈局問題,如圖 5.4 所示。 
每一個按鈕有它自己的功能:
- Align(對齊) – 建立對齊的約束條件,例如:對齊兩個視圖的左側。
- Add new constraints (加入新的約束條件) – 建立間距的約束條件,例如:訂出 UI control 的寬度。
- Embed in – 嵌入視圖至堆疊式圖(stack view)(或其他視圖),我們將在下一章進一步討論堆疊視圖。
- Resolve auto layout issues (解決自動佈局問題) – Resolve layout issues.
- Update frames (更新frames) - 以設定的約束條件來更新frame的位置與尺寸。
如同之前所說明的,要將「Hello World」按鈕置中,你必須要定義兩個約束條件: 「Center Horizontally」與「Center Vertically」,這兩個約束條件都相對於該視圖。
要建立約束條件,我們將會使用「Align」按鈕。首先,選取在介面建構器的「Hello World」按鈕,然後在佈局列中點選「Align」圖示。在彈出式選單中,勾選「 Horizontal in Container」與「 Vertically in Container」選項,接著點選「Add 2 Constraints」按鈕,如圖 5.5 所示。
Quick tip: 你可以按下 command+0 鍵來隱藏專案導覽器,如此可以釋放更多的畫面空間,可有利於 App 設計。
此時你應該會見到一組藍色約束線。如果你在文件大綱(Document Outline )視圖展開「Constraints」選項,你會見到按鈕有兩個新的約束條件。這些約束條件可以確保按鈕總是保持在視圖中心。另外,你也可以在尺寸檢閱器( Size Inspector )中檢視這些約束條件,如圖 5.6 所示。
Quick note: 當你的視圖的佈局設定正確且沒有模糊不清的話,這些約束線是以藍色來呈現。
好的,準備再一次測試 App,你可以點選「執行」( Run )按鈕,分別以iPhone 12 Pro(或是 iPhone SE)來啟動App。另外,只要使用設定列選取其他裝置或者變更裝置的方向, 就可以確認佈局是否正確。現在,不管畫面大小與方向為何,按鈕都應該已經完美置中了。
解決佈局約束條件問題
剛剛我們所設定的佈局約束條件很完美,不過有時候並非都能如此順利,Xcode 可以聰明的偵測出任何約束條件的問題。
試著拖曳「Hello World」按鈕到畫面的左下方,Xcode 可以立刻偵測出一些佈局問題, 同時相對應的約束線會變成橘色,指出錯位的項目,如 圖 5.7 所示。
當你所建立的約束條件有模糊或有所衝突時,便會出現自動佈局問題。依照約束條件的設定,這裡我們指定按鈕應該在容器(也就是視圖)內水平與垂直置中,然而現在按鈕移到視圖的左下角,介面建構器發現了這裡有所模糊,因此它使用了橘色線來標示佈局問題,虛線的部分則表示這個按鈕應該存在的位置。
當有任何的佈局問題,文件大綱視圖會顯示出一個紅/橘色的揭露箭頭(disclosure arrow ),按下這個揭露箭頭,你會看見問題清單,如圖 5.8 所示。對於佈局問題,介面建構器可以聰明地幫助我們解決佈局問題,點選問題旁邊的指示圖示,此時會出現幾個解決方案。以這個例子而言,選取「Update Frame」選項,然後點選「Fix Misplacement」按鈕, 按鈕便會移到視圖的中心了。
另外,你也可以只點選佈局列上的「Update frames」按鈕,來解決這個問題。
這個問題是我自己動手移動所產生的。我只是想要示範如何發現佈局問題並修復它。當你跟著本書的範例來練習時,很有可能會面臨到類似的問題。此時你應該要知道如何輕易且快速地解決佈局問題。
加入一個標籤
現在你對自動佈局應該有了概念,我們來試著加入一個標籤到視圖的右下方,並了解該如何定義這個標籤的約束條件。iOS 中的標籤通常是用於顯示一些文字與訊息。
在介面建構器中,點選元件庫按鈕來開啟元件庫,從元件庫(Object Library)拖曳一個標籤,並將之置放到視圖的右下角。在標籤上點選兩下,將標題改為「Welcome to Auto Layout」,或者任何你想放的文字皆可,如圖5.9 所示。
如果你切換到另一款 iPhone 機型(例如,iPhone 15 Pro Max),你應該會注意到標籤從邊緣移動了一點(見圖 5-10)。 點擊配置欄中的 Orientation 圖標可將設備旋轉為橫向,你就發覺無法在裝置上正確地顯示標籤。
那麼要怎麼解決這個問題呢?很明顯的,我們必須要設定一些約束條件來讓它能夠正確運作。問題是:「我們要加入什麼約束條件呢?」
我們先試著用文字來描述這個標籤的需求。你可以像這樣來描述:
「這個標籤應該要置於視圖的右下角。」
這樣敘述沒有問題,不過還不夠精確。更精確地描述這個標籤的位置的敘述如下:
「這個標籤位於距離視圖右側邊距20 點的位置,且距離視圖底部20 點。」
這樣好多了。當你精確地描述一個項目的位置,你可以很容易想到佈局約束條件。這個標籤的約束條件是:
這個標籤距離視圖右側邊距為 20點。
標籤距離視圖底部是20點。
在自動佈局中,我們以這樣的約束條件作為「間距約束條件」( spacing constraints )。當想要建立這些間距約束條件,你可以使用佈局按鈕中的「Add new constraints」按鈕。不過這次我們使用 control 鍵加上拖曳的方法來應用自動佈局。在介面建構器中,你可以按著 control鍵,從一個項目拖曳至自己,或者拖曳至另一個你想要加入約束條件的項目。
要加入第一個間距約束條件,按住 control 鍵並從標籤拖曳至右側,直到視圖變成藍色為止。現在放開按鈕,你會見到一個彈出式選單,顯示一串約束條件選項。選取「 Trailing space to Safe Area」,來加入從標籤至視圖右側邊距的間距約束條件,如圖 5.11 所示。
在文件大綱視圖中,你會見到新的約束條件。介面建構器現在以紅色顯示約束線,並指出還有一些漏掉的約束條件。這很正常,因為我們還沒有定義第二個約束條件。
現在按住鍵,從標籤拖曳至視圖底部。釋放按鈕並在彈出式選單中選取「Bottom Space to Safe Area」,如此一來,便建立了一個從標籤至視圖底部佈局導引的間距約束條件,如圖 5.12 所示。
當你加入了這兩個約束條件後,所有的線都變成藍實線了。當你預覽 UI 或者在模擬器中執行 App,這個標籤在所有的螢幕尺寸中應該都能夠正確顯示,即使在橫向模式也沒有問題,如圖 5.13 所示。
很棒 !你已經正確地定義約束條件。不過,你可能會注意到在文件大綱中的黃色指示,你將會發現一個和本地化(localization )有關的佈局警告。
這是什麼呢?
我們的 App 目前是顯示支援英文,所以我們所定義的約束條件適用於英文版本,但如果要支援其他語系呢?目前的佈局對於其他語言依然能夠適用嗎(例如:阿拉伯文)?
在 Xcode 15 中,介面建構器將會自動檢視你的佈局約束條件,並了解是否相容於所有語言。當發現問題時,它會發出一個本地化警告,你可以選擇第二個選項來加入前緣約束條件(leading constraint ),如圖 5.14 所示。
安全區域
在文件大綱中,你是否注意到一個稱作「安全區域」(Safe Area )的項目嗎?你還記得我們之前所定義的間距約束條件是和安全區域有關嗎?我們定義兩個間距約束條件:
- 後緣間距(Trailing space)至安全區域。
- 底部間距(Bottom space)至安全區域。
而什麼是安全區域呢?
安全區域是在 Xcode 9 所導入用來替代舊版Xcode 中頂部與底部佈局導引(top & bottom layout guides )的新功能。與其用文字來解釋這些名詞,我們直接示範來說明什麼是安全區域。
至文件大綱中,選取「Safe Area」,藍色的部分就是安全區域。安全區域實際上是一個佈局導引,清楚地呈現不包含列( bar )以及其他內容的視圖。如圖 5.15 所示,除了狀態列(status bar )之外的整個視圖,都是安全區域。
這個安全區域佈局導引可幫助開發者很容易地處理約束條件,因為這個安全區域在遇到被導覽列(navigation bar )或其他內容所覆蓋時會自動更新。
看一下圖 5.16,這個Hello word 按鈕定義為位於安全區域頂部下方 20 點,如果這個視圖沒有導覽列或標籤列(tab bar ),則安全區域是除了狀態列外的整個視圖,因此按鈕是位於狀態列下 20 點。
如果視圖中包含了一個導覽列,不論它是使用標準標題或是大標題,這個安全區域會自動地調整,這個按鈕會移至導覽列的下方。因此,只要 UI 物件的約束條件是相對於安全區域佈局導引,即使你在介面中加入導覽列或者標籤列,你的介面會正確佈局。
編輯約束條件
現在這個「Welcome to Auto Layout」標籤距離安全區域後緣邊距為16 點。而你若是想要在標籤和視圖的右側之間加入一些間距呢?介面建構器提供一個方便的方式來編輯約束條件的常數。
你可以在文件大綱視圖挑選約束條件或者直接選取約束條件。在屬性檢閱器中,你可以找到這個約束條件的屬性,包括了關聯性( Relation )、常數( Constant )以及優先權( Priority )。你可以將常數的值修改為「30」來加入一些間距,如圖 5.17 所示。
另外,你也可以在約束條件點擊兩次,然後透過彈出式選單來編輯它的屬性,如圖 5.18 所示。
你的作業—加入標籤及約束條件
現在,我希望你對如何佈局你的App UI 以及讓它能夠相容各種螢幕尺寸有一些基本概念了。在我們進到下一章之前,我們來做一個簡單的作業,我只要你加入另外兩個表情符號標籤至視圖中。圖 5.19 為預期的結果,這裡有幾個提示:
- 這個「笑臉」表情符號標籤應該距離安全區域頂部33點,並且水平置中。
- 這個「調皮鬼」標籤有兩個間距約束條件。
你可以在屬性檢閱器中編輯它的「Font」選項,來調整標籤的字型大小。不過,即使你不知道該怎麼做也沒關係,我將在之後的章節示範,現在先專注在約束條件的定義。
本章小結
在本章中,我們學到了自動佈局的基礎內容。是的,這只是基礎而已,因為我不希望學習自動佈局後,馬上把你嚇跑。當我們更深入建立真實的App 時,我們將繼續探討自動佈局的其他功能。
多數的初學者(即使是一些有經驗的 iOS 程式設計師)都會避免使用自動佈局,因為它看起來會讓人搞不清楚。但當你完全了解我在本章中所介紹的內容時,你即將踏上成為一個優秀 iOS 開發者的道路。最早的iPhone 是在2007 年推出,經過這些年後,iOS 的發展已經有了許多的變化與進步,不像以前你的App 只需要在3.5 英吋、非視網膜螢幕的裝置上運作這麼單純,現在你必須滿足不同螢幕解析度及螢幕尺寸,這也是為何我以整個章節的篇幅來介紹自動佈局的緣故。
請花點時間來消化這些內容吧 !
本文摘自《iOS 17 App程式設計實戰心法》(Swift+UIKit)》一書。如果你想更深入學習Swift程式設計和下載完整程式碼,你可以從 AppCoda網站 購買完整電子版。