使用SwiftUI Grid實現Bento Grid UI,提升資訊組織與視覺美感

更新 發佈閱讀 17 分鐘

自從我開始從事APP設計開發以來,每年我都會關注Apple的WWDC。這個系列活動會介紹針對不同操作系統的最新軟體與科技。自WWDC20起,Apple將一個主題的所有重點整合在一個畫面中呈現。內容以圓角矩形卡片的形式展現,每張卡片傳達一個特點,卡片大小會根據資訊內容或權重進行調整,並基於網格系統進行動態有序的排版。下圖便是今年WWDC24中關於iOS18最新資訊的介紹。

raw-image

這種Apple慣於用於宣傳新產品特點的排版手法,便是自2023年起流行的Bento Grid設計,中文稱為”便當盒設計“。


Table of contents

  • Bento Grid
  • SwiftUI Grid
  • SwiftUI Bento Grid
  • Conclusion


Bento Grid

Bento Grid的起源

raw-image

如其名稱所示,Bento Grid 的靈感來自日本便當(弁当,bento)盒的分格方式。其特色在於以四方結構為間隔,提供多種菜餚在有限空間內各自擁有獨立的放置區域。這不僅提升了菜餚的承裝數量,也使食用者能一覽所有菜色;在進食過程中,也能方便且優雅地夾取食物。

日本便當展現了實用性與簡潔優雅的設計,體現出純淨的美感,是機能與美感兼具的經典設計範例。這種以使用者(食用者)的角度出發,考量產品整體使用過程(從承裝到食用的體驗)的設計理念,正是「形隨機能」(Form Follows Function)的體現。


Bento Grid 的特點

將便當盒的分隔手法轉化到介面設計上,可以將頁面分隔成大小不一的方塊,以呈現不同的內容。這種設計方式對設計師和使用者都具有以下優點:

模組化與彈性佈局(High Modularity and Flexibility)

  • Bento Grid 中的每個方塊(或稱模組)都可以自由調整大小、形狀和位置,為設計師提供了一個既有規則又不受限制的佈局框架。
  • 當設計師需要在一個畫面上呈現大量資訊時,格狀卡片佈局有助於清晰劃分資訊,並根據內容的重要性和視覺層級靈活調整方塊的大小與排列。

視覺層次與引導(Clear Visual Hierarchy and Guidance)

  • 井然有序的模組化佈局使使用者能快速掃描頁面,找到所需資訊。
  • 透過方塊的大小、位置和顏色的運用,能有效引導使用者的視線至重要內容,形成清晰的視覺層級。
  • 透過方塊排列方式的規劃,可以將相關內容歸類在一起,設計出清晰的視覺路徑,引導使用者的視線流動。


Bento Grid 的設計原則

讓我們以設計師實事求是的精神來檢視Apple WWDC24產品特點總覽的畫面設計,看看它是否遵循了Grid佈局原則。在這裡,我使用Figma的Layout grid進行比對。新增一個Frame元件,並設置畫面尺寸為1194 x 670。接著,在Layout grid中分別新增Columns和Rows:

  • Columns:將Count設置為20,Margin(邊界)和Gutter(分欄間距)各設置為10。
  • Rows:將Count設置為12,Margin(邊界)和Gutter(分欄間距)各設置為10。

同樣地,當你在設計Bento Grid時,可以善用Layout grid來作為網格佈局的參考。

Figma Grid Layout

Figma Grid Layout

可以確定的是,WWDC24產品頁面中的網格完全遵循了Layout grid的系統,並根據資訊內容的層級進行了大小不一的變化。


SwiftUI Grid

SwiftUI 的 Grid 是一種佈局容器,類似於文書工具中常見的表格,可以輕鬆地將內容組織成行(Rows)和列(Columns)。Grid 由三個主要構成元素組成,以下是其關鍵概念的說明:

  1. GridGrid 可以根據螢幕尺寸動態調整內容佈局,自動調整行與列的排列,以適應各種設備和狀況。其中,alignment 參數用於設定列(Columns)的對齊位置;horizontalSpacingverticalSpacing 參數則相當於分欄間距的概念。
  2. GridRowGridRow 用於管理單行中的所有列內容,並可通過新增 GridRow 來增加行(Rows)的數量。
  3. Column:在SwiftUI中無需特別定義Column,因為每個容器(如TextImageShape)本身就充當一個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 Grid

SwiftUI Grid


Grid配置

當然,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是關鍵的配置功能。

SwiftUI Grid gridCellColumn

SwiftUI Grid gridCellColumn


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 gridCellUnsizedAxes

SwiftUI Grid gridCellUnsizedAxes


SwiftUI Bento Grid

接下來,我將展示如何使用SwiftUI Grid來實現Bento Grid。我們將以Apple WWDC24的Bento Grid佈局作為參考,並給每個網格區塊分配一個英文編號以便識別。

WWDC Bento Grid

WWDC 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))
}
  1. 建立Grid容器:設置 horizontalSpacingverticalSpacing,這些間距參數等於gutter的概念。
  2. 建立第一個GridRow:此列包含2個欄位,分別是GridCard B與C的堆疊和GridCard D。GridCard B與C的堆疊欄寬佔據整體Layout的6個網格,設置 .gridCellColumns(6)。GridCard D的欄寬佔據整體Layout的4個網格,設置 .gridCellColumns(4);列高佔據4個網格,設置 .frame(height: gridCellRows(4))
  3. 建立第二個GridRow:此列的列高也佔據4個網格,設置 .frame(height: gridCellRows(4));包含2個欄位,分別是GridCard E和GridCard F。GridCard E佔據2個網格,設置 .gridCellColumns(2)。GridCard F佔據8個網格,設置 .gridCellColumns(8)
raw-image
善用 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))
  1. 建立Grid容器:設置 horizontalSpacingverticalSpacing,這些間距參數等於gutter的概念。
  2. 建立第一個GridRow:此列包含兩個Grid,分別是Grid AL和Grid MR。
  3. Grid AL:內部包含兩個GridRow。第一個GridRow 包含GridCard A 和先前建立的GridBF中的GridRow AF。第二個GridRow 包含GridRow GL。
  4. Grid MR:由四個GridRow組成。基於先前展示的Step 1 - Grid B2F的概念,實現起來會相對容易,這裡不再詳述。

因此,通過靈活運用Grid和GridRow的變化組合,我們可以成功還原Apple WWDC24 Bento Grid的佈局!

SwiftUI Bento Grid

SwiftUI Bento Grid

GridRow 中可以包含多個 Grid,而 Grid 又可以包含多個 GridRow,就像俄羅斯娃娃一樣。掌握了這個概念,我們就能輕鬆實現複雜的Grid佈局。


最後,我將前面仿照WWDC24的Bento Grid套用到oThings的產品特色與功能說明中。結果證明,Bento Grid的佈局方式確實方便設計師規劃內容豐富的界面。透過網格的尺寸變化與配置,能夠提供規律、彈性以及具視覺層次的資訊呈現。這是一種由理性編排支撐的感性視覺美感。

oThings 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介面框架開發,結合設計的創意與程式的創新所創造的混合體驗設計,呈現高質感的動畫轉場與絲滑的互動效果,打造具品牌辨識度的使用體驗。歡迎有興趣的您下載體驗。


Conclusion

透過這次使用SwiftUI Grid實踐Bento Grid的實驗,我深刻體會到SwiftUI Grid的功能雖然簡單,但卻非常強大。以下是幾點關於Bento Grid的心得:

  1. 適合大尺寸屏幕:大尺寸畫面,例如平板或電腦較大的螢幕,能夠容納更多的網格,從而突顯出網格間的變化差異。
  2. 善用Grid與GridRow的組合:雖然GridRow需要被Grid包覆,但內層也可以再包覆Grid,這樣才能創造出複雜的Grid佈局。當處理全新的複雜佈局時,可以先通過簡單的繪圖拆解GridGridRow的結構關係,這樣會更容易進行畫面的設計。
  3. Grid的欄位數量由Grid內定義:與Word或Google Doc中的表格功能不同,SwiftUI Grid的網格欄位數量是由Grid自身定義的。例如,在一個GridRow內放置兩個視圖,對於Grid而言,整體欄位數量為2。當我們對某個欄位定義了 .gridCellColumns(3),那麼整體的欄位數量就會增加到4個。



留言
avatar-img
留言分享你的想法!
avatar-img
Kyle Lei
4會員
8內容數
在這瞬息萬變的世界中,讓設計師們以混合成長思維的視角思考自身的職涯規劃,以混合體驗設計的觀點思考使用者體驗設計。
你可能也想看
Thumbnail
雙11於許多人而言,不只是單純的折扣狂歡,更是行事曆裡預定的,對美好生活的憧憬。 錢錢沒有不見,它變成了快樂,跟讓臥房、辦公桌、每天早晨的咖啡香升級的樣子! 這次格編突擊辦公室,也邀請 vocus「野格團」創作者分享掀開蝦皮購物車的簾幕,「加入購物車」的瞬間,藏著哪些靈感,或是對美好生活的想像?
Thumbnail
雙11於許多人而言,不只是單純的折扣狂歡,更是行事曆裡預定的,對美好生活的憧憬。 錢錢沒有不見,它變成了快樂,跟讓臥房、辦公桌、每天早晨的咖啡香升級的樣子! 這次格編突擊辦公室,也邀請 vocus「野格團」創作者分享掀開蝦皮購物車的簾幕,「加入購物車」的瞬間,藏著哪些靈感,或是對美好生活的想像?
Thumbnail
雙11購物節準備開跑,蝦皮推出超多優惠,與你分享實際入手的收納好物,包括貨櫃收納箱、真空收納袋、可站立筆袋等,並分享如何利用蝦皮分潤計畫,一邊購物一邊賺取額外收入,讓你買得開心、賺得也開心!
Thumbnail
雙11購物節準備開跑,蝦皮推出超多優惠,與你分享實際入手的收納好物,包括貨櫃收納箱、真空收納袋、可站立筆袋等,並分享如何利用蝦皮分潤計畫,一邊購物一邊賺取額外收入,讓你買得開心、賺得也開心!
Thumbnail
分享個人在新家裝潢後,精選 5 款蝦皮上的實用家居好物,包含客製化層架、MIT 地毯、沙發邊桌、分類垃圾桶及寵物碗架,從尺寸、功能到價格都符合需求,並提供詳細開箱心得與購買建議。
Thumbnail
分享個人在新家裝潢後,精選 5 款蝦皮上的實用家居好物,包含客製化層架、MIT 地毯、沙發邊桌、分類垃圾桶及寵物碗架,從尺寸、功能到價格都符合需求,並提供詳細開箱心得與購買建議。
Thumbnail
惠香食品將台灣花磚之美和滿滿的祝福做結合,打造出具有台灣古典雅致之美的台灣花磚手工蛋捲禮盒,無論是當作逢年過節送禮,還是外出拜訪友人的伴手禮,都是體面又大方的最佳選擇。
Thumbnail
惠香食品將台灣花磚之美和滿滿的祝福做結合,打造出具有台灣古典雅致之美的台灣花磚手工蛋捲禮盒,無論是當作逢年過節送禮,還是外出拜訪友人的伴手禮,都是體面又大方的最佳選擇。
Thumbnail
跟風一下 拉波兒麵包的「千層布丁」
Thumbnail
跟風一下 拉波兒麵包的「千層布丁」
Thumbnail
巧克力扁可頌  45元 韓式流行新品 x 多層次可頌 壓扁的可頌淋上脆皮可可醬 有點退流行的扁可頌,最新流行的應該是花瓣蛋塔了吧! 不過都買到還是來開箱一下 字有點小我直接說 因為有巧克力所以沒辦法加熱 因為是扁可頌所以要趕快吃完 打開發現他的個頭不小耶!! 吃到最後有點
Thumbnail
巧克力扁可頌  45元 韓式流行新品 x 多層次可頌 壓扁的可頌淋上脆皮可可醬 有點退流行的扁可頌,最新流行的應該是花瓣蛋塔了吧! 不過都買到還是來開箱一下 字有點小我直接說 因為有巧克力所以沒辦法加熱 因為是扁可頌所以要趕快吃完 打開發現他的個頭不小耶!! 吃到最後有點
Thumbnail
格子鬆餅是一種創意十足的鬆餅變種,其獨特之處在於其外觀。這種鬆餅是在烤盤或鬆餅鍋中烹調的,製作過程中將麵糊倒入格子狀的模具中,使其在烹調過程中形成格子狀的外觀。 製作格子鬆餅的方法與製作普通鬆餅的方法相似,只是在倒入麵糊時需將其均勻地倒入格子狀的模具中,然後在烹調過程中保持格子形狀。這種鬆餅可以搭
Thumbnail
格子鬆餅是一種創意十足的鬆餅變種,其獨特之處在於其外觀。這種鬆餅是在烤盤或鬆餅鍋中烹調的,製作過程中將麵糊倒入格子狀的模具中,使其在烹調過程中形成格子狀的外觀。 製作格子鬆餅的方法與製作普通鬆餅的方法相似,只是在倒入麵糊時需將其均勻地倒入格子狀的模具中,然後在烹調過程中保持格子形狀。這種鬆餅可以搭
Thumbnail
這是一盒小餅乾,是朋友送我的,根本捨不得吃。留給最愛的人享用,我看了以後,心中有無限的感慨,特別是作文化創意的我,在想台灣的糕餅業什麼時候可以做到這個程度。 圖片中是一盒「銀座菊廼舎 冨貴寄 招福 アマビエ 缶」,由 JTB 商事與銀座菊廼舎合作推出的限量版和菓子禮盒。罐身以鮮豔的白色為底,繪製了
Thumbnail
這是一盒小餅乾,是朋友送我的,根本捨不得吃。留給最愛的人享用,我看了以後,心中有無限的感慨,特別是作文化創意的我,在想台灣的糕餅業什麼時候可以做到這個程度。 圖片中是一盒「銀座菊廼舎 冨貴寄 招福 アマビエ 缶」,由 JTB 商事與銀座菊廼舎合作推出的限量版和菓子禮盒。罐身以鮮豔的白色為底,繪製了
Thumbnail
麥當當以Hello Kitty形象封裝套餐包裝盒、紙袋、杯子,慶祝金龍年。這些商品不僅展現出可愛的卡通角色,還象徵著好運和吉祥。文章介紹了商品的特色和吉祥象徵,呼應了金龍年的祝福和美食享受。
Thumbnail
麥當當以Hello Kitty形象封裝套餐包裝盒、紙袋、杯子,慶祝金龍年。這些商品不僅展現出可愛的卡通角色,還象徵著好運和吉祥。文章介紹了商品的特色和吉祥象徵,呼應了金龍年的祝福和美食享受。
Thumbnail
自從學會在烘焙教室做麵包後,常在烘焙行買材料自己在家做麵包。驅塵氏強韌密封夾鏈袋保鮮效果極佳,採雙軌夾鍊設計能有效防潮、阻隔氣味。除了烘焙材料,M、L兩種尺寸亦適合儲存食材。可重複使用,環保又省錢。
Thumbnail
自從學會在烘焙教室做麵包後,常在烘焙行買材料自己在家做麵包。驅塵氏強韌密封夾鏈袋保鮮效果極佳,採雙軌夾鍊設計能有效防潮、阻隔氣味。除了烘焙材料,M、L兩種尺寸亦適合儲存食材。可重複使用,環保又省錢。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News