以前,建構用於 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