自從我開始從事APP設計開發以來,每年我都會關注Apple的WWDC。這個系列活動會介紹針對不同操作系統的最新軟體與科技。自WWDC20起,Apple將一個主題的所有重點整合在一個畫面中呈現。內容以圓角矩形卡片的形式展現,每張卡片傳達一個特點,卡片大小會根據資訊內容或權重進行調整,並基於網格系統進行動態有序的排版。下圖便是今年WWDC24中關於iOS18最新資訊的介紹。
這種Apple慣於用於宣傳新產品特點的排版手法,便是自2023年起流行的Bento Grid設計,中文稱為”便當盒設計“。
如其名稱所示,Bento Grid 的靈感來自日本便當(弁当,bento)盒的分格方式。其特色在於以四方結構為間隔,提供多種菜餚在有限空間內各自擁有獨立的放置區域。這不僅提升了菜餚的承裝數量,也使食用者能一覽所有菜色;在進食過程中,也能方便且優雅地夾取食物。
日本便當展現了實用性與簡潔優雅的設計,體現出純淨的美感,是機能與美感兼具的經典設計範例。這種以使用者(食用者)的角度出發,考量產品整體使用過程(從承裝到食用的體驗)的設計理念,正是「形隨機能」(Form Follows Function)的體現。
將便當盒的分隔手法轉化到介面設計上,可以將頁面分隔成大小不一的方塊,以呈現不同的內容。這種設計方式對設計師和使用者都具有以下優點:
模組化與彈性佈局(High Modularity and Flexibility):
視覺層次與引導(Clear Visual Hierarchy and Guidance):
讓我們以設計師實事求是的精神來檢視Apple WWDC24產品特點總覽的畫面設計,看看它是否遵循了Grid佈局原則。在這裡,我使用Figma的Layout grid進行比對。新增一個Frame元件,並設置畫面尺寸為1194 x 670。接著,在Layout grid中分別新增Columns和Rows:
同樣地,當你在設計Bento Grid時,可以善用Layout grid來作為網格佈局的參考。
可以確定的是,WWDC24產品頁面中的網格完全遵循了Layout grid的系統,並根據資訊內容的層級進行了大小不一的變化。
SwiftUI 的 Grid
是一種佈局容器,類似於文書工具中常見的表格,可以輕鬆地將內容組織成行(Rows)和列(Columns)。Grid
由三個主要構成元素組成,以下是其關鍵概念的說明:
Grid
可以根據螢幕尺寸動態調整內容佈局,自動調整行與列的排列,以適應各種設備和狀況。其中,alignment
參數用於設定列(Columns)的對齊位置;horizontalSpacing
和 verticalSpacing
參數則相當於分欄間距的概念。GridRow
用於管理單行中的所有列內容,並可通過新增 GridRow
來增加行(Rows)的數量。Text
、Image
或Shape
)本身就充當一個Column。Grid(alignment: .center, horizontalSpacing: gutter, verticalSpacing: gutter) {
GridRow {
Text("Row 1 / Column 1")
Text("Row 1 / Column 2")
}
GridRow {
Text("Row 2 / Column 1")
Text("Row 2 / Column 2")
}
}
當然,SwiftUI 提供了一些網格修飾符,可以讓我們設計出更具彈性和進階功能的表格。以下是兩個我實際使用過的配置功能:
gridCellColumn:使用 .gridCellColumns(_:)
修飾符可以讓某個網格橫跨多列,實現類似於 Word 中的跨欄或 Google Doc 中的合併儲存格效果。
GridRow {
Text("Row 1 / Column 1")
.padding()
.background(Color.gray, in: RoundedRectangle(cornerRadius: 10))
.gridCellColumns(2)
}
gridCellColumns(2)
內的參數2代表著"Column 1"橫跨2個欄位,加上 "Column 2"佔據1個Column,整個GridRow則創造了3個Column。這個概念對於稍後要實現的Bento Grid是關鍵的配置功能。
gridCellUnsizedAxes:如果需要將網格的高度或寬度設置為無限大,以填滿整個欄位空間,可以使用 gridCellUnsizedAxes(_:)
修飾符來指定網格的適當大小。否則,網格可能會無限延伸,佔據整個屏幕。
GridRow {
Text("Row 1 / Column 1")
.padding()
.frame(maxWidth: .infinity)
.gridCellUnsizedAxes(.horizontal)
.background(Color.gray, in: RoundedRectangle(cornerRadius: 10))
.gridCellColumns(2)
}
對於 "Row 1 / Column 1",使用了 .frame(maxWidth: .infinity)
並搭配 gridCellUnsizedAxes(.horizontal)
,以指定其寬度必須根據內容適當調整。這樣,網格只會填滿2個欄位的寬度,而不會無限延伸。
接下來,我將展示如何使用SwiftUI Grid來實現Bento Grid。我們將以Apple WWDC24的Bento Grid佈局作為參考,並給每個網格區塊分配一個英文編號以便識別。
為了容易理解,讓我們先實作Grid B 到Grid F的局部佈局。
Grid(alignment: .leading, horizontalSpacing: gutter, verticalSpacing: gutter) {
//GridRow that contains GridCard B&C and GridCard D
GridRow {
VStack(spacing: gutter) {
GridCard("B")
.frame(height: cellSize?.height)
GridCard("C")
.frame(height: cellSize?.height)
}
.gridCellColumns(6)
GridCard("D")
.gridCellColumns(4)
.frame(height: gridCellRows(4))
}
//GridRow that contains GridCard E and GridCard F
GridRow {
GridCard("E")
.gridCellColumns(2)
.readSize { value in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
//Use PreferenceKey to read the basic grid width
cellSize = CGSize(width: value.width, height: value.width)
}
}
GridCard("F")
.gridCellColumns(8)
}
.frame(height: gridCellRows(4))
}
horizontalSpacing
和 verticalSpacing
,這些間距參數等於gutter的概念。.gridCellColumns(6)
。GridCard D的欄寬佔據整體Layout的4個網格,設置 .gridCellColumns(4)
;列高佔據4個網格,設置 .frame(height: gridCellRows(4))
。.frame(height: gridCellRows(4))
;包含2個欄位,分別是GridCard E和GridCard F。GridCard E佔據2個網格,設置 .gridCellColumns(2)
。GridCard F佔據8個網格,設置 .gridCellColumns(8)
。善用 gridCellColumns 修飾符可以定義網格單元在Grid容器中所佔的欄位數量,讓我們自由地調整網格的欄寬。
有了基本的Grid概念後,就讓我們接著完成整個Grid佈局。
Grid(alignment: .center, horizontalSpacing: gutter, verticalSpacing: gutter) {
//GridRow that contains Grid AL and Grid AL
GridRow {
//Grid AL that contains GridRow AF and GridRow MR
Grid(alignment: .leading, horizontalSpacing: gutter, verticalSpacing: gutter) {
//GridRow AF that contains GridCard A and GridBF B2F
GridRow {
GridCard("A")
.gridCellColumns(4)
.frame(height: gridCellRows(8))
GridBF()
.gridCellColumns(10)
}
//GridRow GL that contains GridCard G2L
GridRow {
GridCardGL()
}
.frame(height: gridCellRows(4))
}
.gridCellColumns(14)
//Grid MR that contains GridCard M2R
Grid(alignment: .leading, horizontalSpacing: gutter, verticalSpacing: gutter) {
GridCardMR()
}
.frame(height: gridCellRows(12))
.gridCellColumns(6)
}
}
.frame(height: gridCellRows(12))
horizontalSpacing
和 verticalSpacing
,這些間距參數等於gutter的概念。因此,通過靈活運用Grid和GridRow的變化組合,我們可以成功還原Apple WWDC24 Bento Grid的佈局!
GridRow 中可以包含多個 Grid,而 Grid 又可以包含多個 GridRow,就像俄羅斯娃娃一樣。掌握了這個概念,我們就能輕鬆實現複雜的Grid佈局。
最後,我將前面仿照WWDC24的Bento Grid套用到oThings的產品特色與功能說明中。結果證明,Bento Grid的佈局方式確實方便設計師規劃內容豐富的界面。透過網格的尺寸變化與配置,能夠提供規律、彈性以及具視覺層次的資訊呈現。這是一種由理性編排支撐的感性視覺美感。
附帶一提,中間oThings logo卡片的漸層背景使用了iOS18推出的MeshGradient
漸層功能,這是一個非常強大的漸層效果。通過以下程式碼,你可以定義網格的格數、座標與顏色參數,輕鬆設計出優美的漸層視覺效果。
MeshGradient(width: 2, height: 2, points: [
[0, 0], [1, 0], [0, 1], [1, 1]
], colors: [
.cyan, .yellow, .green, .purple
])
oThings是Kyle獨立設計與開發的APP, 是一款專為您的生活記憶而設的記憶保存應用程式,讓您專注於記錄珍愛的事物。以SwiftUI介面框架開發,結合設計的創意與程式的創新所創造的混合體驗設計,呈現高質感的動畫轉場與絲滑的互動效果,打造具品牌辨識度的使用體驗。歡迎有興趣的您下載體驗。
透過這次使用SwiftUI Grid實踐Bento Grid的實驗,我深刻體會到SwiftUI Grid的功能雖然簡單,但卻非常強大。以下是幾點關於Bento Grid的心得:
GridRow
需要被Grid
包覆,但內層也可以再包覆Grid
,這樣才能創造出複雜的Grid佈局。當處理全新的複雜佈局時,可以先通過簡單的繪圖拆解Grid
與GridRow
的結構關係,這樣會更容易進行畫面的設計。GridRow
內放置兩個視圖,對於Grid
而言,整體欄位數量為2。當我們對某個欄位定義了 .gridCellColumns(3)
,那麼整體的欄位數量就會增加到4個。