從 iOS 16 開始,SwiftUI 推出了 AnyLayout
和 Layout
協定,讓開發者構建客製化和複雜的 layout。AnyLayout
是 layout 協定的 type-erased 實例。我們可以使用 AnyLayout
來創建動態 layout,它可以回應使用者的互動或環境變化。
在這篇教學文章中,我們會看看如何使用 AnyLayout
來切換垂直和水平 layout。
使用 AnyLayout
首先,讓我們用 App 模板創建一個新的 Xcode 專案,並為專案命名,我會把專案命名為 SwiftUIAnyLayout
。我們會構建一個簡單的範例 App,在使用者點擊堆疊視圖時切換 UI layout。UI layout 在不同方向看起來會是這樣的:
在開始時,範例 App 利用 VStack
把三個圖像垂直排列。當使用者點擊堆疊視圖時,就會變成水平堆疊。我們可以這樣使用 AnyLayout
來實作:
struct ContentView: View {
@State private var changeLayout = false
var body: some View {
let layout = changeLayout ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout())
layout {
Image(systemName: "bus")
.font(.system(size: 80))
.frame(width: 120, height: 120)
.background(in: RoundedRectangle(cornerRadius: 5.0))
.backgroundStyle(.green)
.foregroundColor(.white)
Image(systemName: "ferry")
.font(.system(size: 80))
.frame(width: 120, height: 120)
.background(in: RoundedRectangle(cornerRadius: 5.0))
.backgroundStyle(.yellow)
.foregroundColor(.white)
Image(systemName: "scooter")
.font(.system(size: 80))
.frame(width: 120, height: 120)
.background(in: RoundedRectangle(cornerRadius: 5.0))
.backgroundStyle(.indigo)
.foregroundColor(.white)
}
.animation(.default, value: changeLayout)
.onTapGesture {
changeLayout.toggle()
}
}
}
我們定義了一個 layout 變數,來保存 AnyLayout
的實例。這個 layout 會根據 changeLayout
的數值,來切換水平和垂直 layout。HStackLayout
(或 VStackLayout
)的行為與 HStack
(或 VStack
)類似;但因為它符合 Layout
協定,我們就可以在 conditional layout 中使用它。
我們還可以把動畫附加到 layout,來動畫化佈局的改變。現在,當我們點擊堆疊視圖時,它就會切換垂直或水平 layout。
根據裝置的方向切換 layout
現在,範例 App 讓使用者點擊堆疊視圖來切換 layout。在某些 App 中,我們可能會想根據裝置方向和螢幕大小來切換 layout。在這個情況下,就可以利用 .horizontalSizeClass
變數來捕捉裝置方向的改變。
@Environment(\.horizontalSizeClass) var horizontalSizeClass
然後,讓我們如此更新 layout
變數:
let layout = horizontalSizeClass == .regular ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout())
舉個例子,如果我們把 iPhone 14 Pro Max 轉為橫向,layout 就會切換為橫向堆疊視圖。
在大多數情況下,我們會使用 SwiftUI 內建的 layout container 來創建 layout,像是 HStackLayout
和 VStackLayout
。但如果這些 layout container 無法實作我們需要的 layout 型別,該怎麼辦呢?iOS 16 引入的 Layout 協定就讓我們可以定義自己客製化的 layout。我們只需要創建一個符合 Layout
協定的型別,並實作以下所需的方法,來定義一個客製化的 layout container:
sizeThatFits(proposal:subviews:cache:)
:這個方法會報告合成 layout 視圖的大小。placeSubviews(in:proposal:subviews:cache:)
:這個方法會為 container 的子視圖分配位置。
推出了 AnyLayout
後,我們只需要幾行程式碼就可以客製化或更改 UI layout,這絕對可以幫助我們構建更優雅和吸引的 UI。在這篇文章的範例 App 中,大家都學會了如何根據螢幕方向切換 layout。其實同樣的技術也可以應用於其他情況中,例如是 Dynamic Type 的大小。
備註:如果你有興趣深入學習 SwiftUI,並參閱所有源程式碼,歡迎看看我們的《精通 SwiftUI》一書,我們已為 iOS 16 和 Xcode 14 全面更新了這本書。