利用 SwiftUI 建立 Expandable List View 並探索 Inset Grouped 樣式!


SwiftUI 列表視圖 (list view) 和 UIKit 的 UITableView 很類似。在 SwiftUI 最初的版本中,Apple 的工程師已經將創建列表視圖的過程變得輕而易舉,我們不需要創建 prototype cell,也不需要委派 (delegate) 或 data source 的協定。我們只需要用幾行程式碼,就可以使用客製化單元格來建構一個列表視圖。在 iOS 14 中,Apple 繼續改善列表視圖,並添加了一些新功能。在本篇教學文章中,我們將會看看如何建構一個展開式列表視圖 (expandable list view) 或大綱視圖 (outline view),並探索 inset grouped 的列表樣式 (list style)。

建構展開式列表視圖

首先,讓我們看看本篇教學的成品。我十分喜歡 La Marzocco,所以我用了它網站的導覽選單 (navigation menu) 為例子。以下的列表視圖展示了選單的大綱,使用者可以點擊顯示按鈕來展開列表。

當然,你也可以用自己的實作方法,來建構這個大綱視圖。但在最新版本的 SwiftUI 中,Apple 讓開發者可以更簡單地建構這種大綱視圖,並自動適應於 iOS、iPadOS、和 macOS 版本。

要讓列表視圖可以展開,你只需要如此建立一個數據模型 (data model)。

struct MenuItem: Identifiable {
    var id = UUID()
    var name: String
    var image: String
    var subMenuItems: [MenuItem]?
}

在上面的程式碼中,我們有一個用來建造選單物件的結構 (struct)。要創建一個巢狀列表 (nested list),關鍵就是要加入一個屬性,包含子級的可選陣列 (optional array of children) subMenuItems。請注意,子級與其父級的型別 (type) 是一樣的。

對於頂層 (top level) 選單物件,我們可以如此創建一個 MenuItem 陣列:

// Main menu items
let sampleMenuItems = [ MenuItem(name: "Espresso Machines", image: "linea-mini", subMenuItems: espressoMachineMenuItems),
                        MenuItem(name: "Grinders", image: "swift-mini", subMenuItems: grinderMenuItems),
                        MenuItem(name: "Other Equipment", image: "espresso-ep", subMenuItems: otherMenuItems)
                    ]

我們會針對每個選單物件,指定子選單 (sub-menu) 物件的陣列。如果沒有子選單物件,則可以省略 subMenuItems 參數,或傳遞 nil 值。我們可以這樣定義子選單物件:

// Sub-menu items for Espressco Machines
let espressoMachineMenuItems = [ MenuItem(name: "Leva", image: "leva-x", subMenuItems: [ MenuItem(name: "Leva X", image: "leva-x"), MenuItem(name: "Leva S", image: "leva-s") ]),
                                 MenuItem(name: "Strada", image: "strada-ep", subMenuItems: [ MenuItem(name: "Strada EP", image: "strada-ep"), MenuItem(name: "Strada AV", image: "strada-av"), MenuItem(name: "Strada MP", image: "strada-mp"), MenuItem(name: "Strada EE", image: "strada-ee") ]),
                                 MenuItem(name: "KB90", image: "kb90"),
                                 MenuItem(name: "Linea", image: "linea-pb-x", subMenuItems: [ MenuItem(name: "Linea PB X", image: "linea-pb-x"), MenuItem(name: "Linea PB", image: "linea-pb"), MenuItem(name: "Linea Classic", image: "linea-classic") ]),
                                 MenuItem(name: "GB5", image: "gb5"),
                                 MenuItem(name: "Home", image: "gs3", subMenuItems: [ MenuItem(name: "GS3", image: "gs3"), MenuItem(name: "Linea Mini", image: "linea-mini") ])
                                ]

// Sub-menu items for Grinder
let grinderMenuItems = [ MenuItem(name: "Swift", image: "swift"),
                         MenuItem(name: "Vulcano", image: "vulcano"),
                         MenuItem(name: "Swift Mini", image: "swift-mini"),
                         MenuItem(name: "Lux D", image: "lux-d")
                        ]

// Sub-menu items for other equipment
let otherMenuItems = [ MenuItem(name: "Espresso AV", image: "espresso-av"),
                         MenuItem(name: "Espresso EP", image: "espresso-ep"),
                         MenuItem(name: "Pour Over", image: "pourover"),
                         MenuItem(name: "Steam", image: "steam")
                        ]

準備好數據模型後,我們可以編寫程式碼以呈現列表視圖。List 視圖現在有了可選的 children 參數,所以如果有任何子物件,你可以提供其 key path \.subMenuItems。然後,SwiftUI 將遞歸 (recursively) 查找子選單物件,並以大綱形式顯示。以下是範例程式碼:

List(sampleMenuItems, children: \.subMenuItems) { item in
    HStack {
        Image(item.image)
            .resizable()
            .scaledToFit()
            .frame(width: 50, height: 50)

        Text(item.name)
            .font(.system(.title3, design: .rounded))
            .bold()
    }
}

List 視圖閉包中,我們會描述每一行的外觀。在範例程式碼中,我們使用了 HStack 佈局圖像和文本視圖。如果你已經在 ContentView 中正確地添加了程式碼的話,SwiftUI 應該會如此呈現大綱視圖:

swiftui-expandable-list-iphone-demo

使用 Inset Grouped 列表樣式

在 iOS 13 中,Apple 為 UITableView 帶來了一種新樣式 ── Inset Grouped,這個樣式會讓每個 section 的內容被包覆在一個圓角區塊內。但是,這種樣式不適用於 SwiftUI 框架中的 List 視圖。在即將發佈的 iOS 14 版本,Apple 就將這個新樣式添加到了 SwiftUI 列表中。

要使用這個新列表樣式,我們可添加 listStyle 修飾符 (modifier) 到 List 視圖,並向它傳遞 InsetGroupedListStyle 的實例:

List {
  ...
}
.listStyle(InsetGroupedListStyle())

如果你正確地跟著步驟做,列表視圖應該會變成 inset grouped 樣式:

swiftui-inset-grouped-style-list

總結

在本篇教學中,我們介紹了 SwiftUI 列表視圖的兩個新功能。如你在範例中可見,要建構一個大綱視圖或展開式列表視圖非常容易,你要做的就只是準備好一個正確的數據模型,列表視圖就會幫你完成剩下的工作,遍歷數據結構,並呈現大綱視圖。

除了這些新功能外,Apple 還在 iOS 14 中加入了另一種列表樣式 SidebarListStyle。而且,新的更新也提供了 OutlineGroupDisclosureGroup,讓我們可以進一步客製大綱視圖。我們將在另一篇教學文章中進一步研究這些功能,請密切關注我們的更新。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。
原文:Building an Expandable List View with Inset Grouped Style Using SwiftUI


軟件工程師,AppCoda 創辦人。著有《iOS 13 App 程式設計實力超進化實戰攻略》、《iOS 13 App 程式設計實力超進化實戰攻略》以及《精通 SwiftUI》。曾任職於HSBC, FedEx等公司,專責軟體開發、系統設計。2012年創立AppCoda技術部落格,定期發表iOS程式教學文章。現時專注發展AppCoda,致力於iOS程式教學,產品設計及開發。

blog comments powered by Disqus
Shares
Share This