利用尺寸類別 (Size Classes) 建構自適應佈局 靈活為不同螢幕尺寸做開發


本篇原文(標題:Building Adaptive Layout with Size Classes — Programmatically)刊登於作者 Medium,由 Batikan 所著,並授權翻譯及轉載。

以前,建構用於 iOS 的使用者介面非常容易,因為只有一種 iPhone 尺寸。但隨著時代的演進,我們現在會遇到許多不同 iPhone 的螢幕尺寸以及方向。

試想像一下,當我們要把在佈局顯示於不同尺寸的螢幕上,要如何對佈局進行調整?

為了達到這個目的,我們必須針對每個視圖元素,開發各自的條件 (condition) 和控制機制 (control mechanism) 才行。

Apple 從 iOS 8 開始導入了尺寸類別 (Size Class),用來統整所有裝置的螢幕尺寸和方向,並區分為兩個類別。

簡單來說,Apple 提出了一個新的範例。與其去根據多種裝置類型、解析度、多任務處理模式、裝置方向來評估佈局,你應該專注於將佈局設置為兩種寬度類型(分別稱為 Compact 和 Regular),和兩種高度類型(同樣稱為 Compact 和 Regular),這些區別稱為尺寸類別。接著,你就可以使用這些尺寸類別來定義或調整佈局。

尺寸類別將所有可能的水平和垂直配置簡化為 Compact 和 Regular 類型。Compact 用於較有限的空間;而 Regular 則用於較不受限制的空間。

在下面的圖中,我們可以看到尺寸類別如何對應到不同裝置與裝置方向。

不同的尺寸類別組合可根據屏幕尺寸,適用於不同裝置上的全螢幕體驗。

size-class-1

使用尺寸類別

在本篇文章中,我將會協助你以編程的方式來創建自適應佈局。

先創建專案並把 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)
}

然後,像下面這樣創建另一個函式,它包含了三個約束條件的陣列。

  1. sharedConstraints,一個用來儲存所有共有約束變動的陣列。
  2. regularConstraints,一個用來儲存所有 Regular 變動的陣列。
  3. 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 方法中呼叫這些函式:

  1. setupUI 用來準備 UI。
  2. setupConsraints 用來準備所有的自動佈局的約束。
  3. 接著,激活共有的約束。
  4. 最後一行程式碼,是用來獲得當前的 traitCollection 並且將它傳送給 layoutTrait 方法。
override func viewDidLoad() {
    super.viewDidLoad()

    setupUI()
    setupConstraints()

    NSLayoutConstraint.activate(sharedConstraints)
    layoutTrait(traitCollection: UIScreen.main.traitCollection)
}

我們成功改變佈局了!

總結

瞭解更多有關尺寸類別的知識,你就能夠更靈活地針對不同螢幕尺寸來做開發,尺寸類別的特色,就是能夠支援多種裝置以及方向。

如果你想要知道更多的訊息,可以參考 Apple 官方的連結

如果你有任何的建議或問題,歡迎在下方留言!謝謝大家。

本篇原文(標題:Building Adaptive Layout with Size Classes — Programmatically)刊登於作者 Medium,由 Batikan 所著,並授權翻譯及轉載。

作者簡介:Batikan,Loodos 的 iOS 開發人員。

譯者簡介:HengJay,iOS 初學者,閒暇之餘習慣透過線上 MOOC 資源學習新的技術,喜歡 Swift 平易近人的語法也喜歡狗狗,目前參與生醫領域相關應用的 App 開發,希望分享文章的同時也能持續精進自己的基礎。

LinkedIn: https://www.linkedin.com/in/hengjiewang/
Facebook: https://www.facebook.com/hengjie.wang


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This