利用尺寸類別 (Size Classes) 建構自適應佈局 靈活為不同螢幕尺寸做開發
以前,建構用於 iOS 的使用者介面非常容易,因為只有一種 iPhone 尺寸。但隨著時代的演進,我們現在會遇到許多不同 iPhone 的螢幕尺寸以及方向。
試想像一下,當我們要把在佈局顯示於不同尺寸的螢幕上,要如何對佈局進行調整?
為了達到這個目的,我們必須針對每個視圖元素,開發各自的條件 (condition) 和控制機制 (control mechanism) 才行。
Apple 從 iOS 8 開始導入了尺寸類別 (Size Class),用來統整所有裝置的螢幕尺寸和方向,並區分為兩個類別。
簡單來說,Apple 提出了一個新的範例。與其去根據多種裝置類型、解析度、多任務處理模式、裝置方向來評估佈局,你應該專注於將佈局設置為兩種寬度類型(分別稱為 Compact 和 Regular),和兩種高度類型(同樣稱為 Compact 和 Regular),這些區別稱為尺寸類別。接著,你就可以使用這些尺寸類別來定義或調整佈局。
尺寸類別將所有可能的水平和垂直配置簡化為 Compact 和 Regular 類型。Compact 用於較有限的空間;而 Regular 則用於較不受限制的空間。
在下面的圖中,我們可以看到尺寸類別如何對應到不同裝置與裝置方向。
不同的尺寸類別組合可根據屏幕尺寸,適用於不同裝置上的全螢幕體驗。

使用尺寸類別
在本篇文章中,我將會協助你以編程的方式來創建自適應佈局。
先創建專案並把 main.storyboard 移到垃圾桶之中,然後讓我們開始設定基本的介面和約束吧!
現在,我將會創建兩個圖像,並使用自動佈局對它們進行排序。接著,我將會確保這兩張圖像適用於不同 iPhone 裝置的方向。
創建三個不同的陣列,裡面都包含了 NSLayoutConstraint 類別,看起來應該會向下面這樣。我們將會根據裝置的方向,來儲存約束條件。
private var compactConstraints: [NSLayoutConstraint] = [] private var regularConstraints: [NSLayoutConstraint] = [] private var sharedConstraints: [NSLayoutConstraint] = []
接著來創建一些 UI 元件,你將會看到總共有三個元件:viewContainer 、image1、以及 image2。
private lazy var viewContainer: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() private lazy var image1: UIImageView = { let image1 = UIImageView() image1.translatesAutoresizingMaskIntoConstraints = false image1.image = UIImage(named: "apple") image1.contentMode = .scaleAspectFit return image1 }() private lazy var image2: UIImageView = { let image2 = UIImageView() image2.translatesAutoresizingMaskIntoConstraints = false image2.image = UIImage(named: "swift") image2.contentMode = .scaleAspectFit return image2 }()
創建一個名為 setupUI 的函式,它會收集所有視圖,並將它們添加到超級視圖當中。
func setupUI() { view.addSubview(viewContainer) viewContainer.addSubview(image1) viewContainer.addSubview(image2) }
然後,像下面這樣創建另一個函式,它包含了三個約束條件的陣列。
- sharedConstraints,一個用來儲存所有共有約束變動的陣列。
- regularConstraints,一個用來儲存所有 Regular 變動的陣列。
- compactConstraints,一個用來儲存所有 Compact 變動的陣列。
func setupConstraints() { sharedConstraints.append(contentsOf: [ viewContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 15), viewContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15), viewContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -15), viewContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 15), image1.leadingAnchor.constraint(equalTo: view.leadingAnchor), image1.topAnchor.constraint(equalTo: view.topAnchor), image2.bottomAnchor.constraint(equalTo: view.bottomAnchor), image2.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) regularConstraints.append(contentsOf: [ image1.trailingAnchor.constraint(equalTo: image2.leadingAnchor, constant: -10), image1.bottomAnchor.constraint(equalTo: view.bottomAnchor), image2.topAnchor.constraint(equalTo: view.topAnchor), ]) compactConstraints.append(contentsOf: [ image1.bottomAnchor.constraint(equalTo: image2.topAnchor, constant: -10), image1.trailingAnchor.constraint(equalTo: view.trailingAnchor), image2.leadingAnchor.constraint(equalTo: view.leadingAnchor) ]) }
來到這篇教學最重要的一部分了!這個方法使用選定的 iPhone 螢幕尺寸及方向來進行自動佈局,透過發送特徵收集 (traitCollection) 參數,來決定尺寸類別。
func layoutTrait(traitCollection:UITraitCollection) { if (!sharedConstraints[0].isActive) { // activating shared constraints NSLayoutConstraint.activate(sharedConstraints) } if traitCollection.horizontalSizeClass == .compact && traitCollection.verticalSizeClass == .regular { if regularConstraints.count > 0 && regularConstraints[0].isActive { NSLayoutConstraint.deactivate(regularConstraints) } // activating compact constraints NSLayoutConstraint.activate(compactConstraints) } else { if compactConstraints.count > 0 && compactConstraints[0].isActive { NSLayoutConstraint.deactivate(compactConstraints) } // activating regular constraints NSLayoutConstraint.activate(regularConstraints) } }
覆寫 traitCollectionDidChange
方法,如此一來當 iPhone 轉換到不同方向時就會通知此方法。
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) layoutTrait(traitCollection: traitCollection) }
最後一個步驟,我們在 viewDidLoad
方法中呼叫這些函式:
setupUI
用來準備 UI。setupConsraints
用來準備所有的自動佈局的約束。- 接著,激活共有的約束。
- 最後一行程式碼,是用來獲得當前的 traitCollection 並且將它傳送給 layoutTrait 方法。
override func viewDidLoad() { super.viewDidLoad() setupUI() setupConstraints() NSLayoutConstraint.activate(sharedConstraints) layoutTrait(traitCollection: UIScreen.main.traitCollection) }
我們成功改變佈局了!
總結
瞭解更多有關尺寸類別的知識,你就能夠更靈活地針對不同螢幕尺寸來做開發,尺寸類別的特色,就是能夠支援多種裝置以及方向。
如果你想要知道更多的訊息,可以參考 Apple 官方的連結。
如果你有任何的建議或問題,歡迎在下方留言!謝謝大家。
LinkedIn: https://www.linkedin.com/in/hengjiewang/
Facebook: https://www.facebook.com/hengjie.wang