較早之前,Apple 正式發佈了 iOS 13。當中除了深色模式 (Dark mode) 和其他新功能外,最新版本的 iOS 還展示了一種與設備互動的新方法,就是 Context Menu 。
Context Menu 功能與 3D Touch 中的 Peek & Pop 類似。兩者其中一個最大的分別,就是此功能可在所有運行 iOS 13 及以上版本的設備上使用,即使該設備不支持 3D Touch 亦可。如果設備支援 3D Touch,大家就可以使用 Touch and Hold 手勢或 Force Touch,來顯示 Context Menu。
如果設備已升級到 iOS 13,你就可以在大多數內建 App 中找到這個新控件,例如「地圖」和「照片」。
在這篇教學中,讓我們一起看看如何在 SwiftUI 實作 Context Menu!
在 SwiftUI 構建 Context Menu
首先,請下載初始專案,並在 Mac 上解壓縮。打開 SwiftUIList.xcodeproj
檔案,並運行專案或在畫布 (canvas) 中預覽,App 應該會顯示一個餐廳列表。
我們要做的,就為這個範例 App 建立一個 Context。我們希望當使用者按住任何一行時,就可以觸發 Context Menu。在菜單中,它提供了兩個操作按鈕供使用者選擇 —— Delete 和 Favorite。如果使用者點擊 Delete 按鈕,該行就會從列表中被刪除;而如果使用者點擊 Favorite 按鈕,該行就會有星號標記。
使用 ContextMenu 修飾符
有了 SwiftUI,Context Menu 的實作就變得非常簡單!你需要做的就只是將 contextMenu
容器 (container) 附加到一個視圖上,並設置菜單選項。
要在 Context Menu 中顯示 Delete 和 Favorite 兩個選項,我們可以如此將 contextMenu
附加到列表中的每一行:
List {
ForEach(restaurants) { restaurant in
BasicImageRow(restaurant: restaurant)
.contextMenu {
Button(action: {
// delete the selected restaurant
}) {
HStack {
Text("Delete")
Image(systemName: "trash")
}
}
Button(action: {
// mark the selected restaurant as favorite
}) {
HStack {
Text("Favorite")
Image(systemName: "star")
}
}
}
}
}
實作按鈕操作 (Button Action)
雖然到目前為止,我們尚未實作任何按鈕操作,但如果你執行 App,並接住其中一行時,App 已經會彈出 Context Menu。
現在,讓我們繼續實作 Delete 的刪除操作。與 onDelete
處置器 (handler) 不同,contextMenu
不會為我們提供所選餐廳的索引 (index)。要弄清楚,我們就需要做幾個步驟。先在 ContentView
中創建一個新函數:
private func delete(item restaurant: Restaurant) {
if let index = self.restaurants.firstIndex(where: { $0.id == restaurant.id }) {
self.restaurants.remove(at: index)
}
}
這個 delete
函數會接收一個餐廳物件,並在 restaurants
陣列 (array) 中搜索其索引。為了找到索引,我們呼叫 firstIndex
函數,並指定搜索條件。該函數的作用就是搜索陣列,並將所選餐廳的 ID 與陣列中的 ID 進行比較。如果有匹配的 ID, firstIndex
函數就會回傳所選餐廳的索引。有了索引之後,我們就可以通過調用 remove(at:)
,將所選餐廳從 restaurants
陣列中刪除。
接下來,在 // delete the selected restaurant
下插入以下這行程式碼:
self.delete(item: restaurant)
當使用者點選 Delete 按鈕時,我們只需要呼叫 delete
函數。
為了可以成功刪除該行,我們還需要使用 @State
關鍵字標記 restaurants
陣列:
@State var restaurants = [ ... ]
現在,我們可以測試 App 了!點擊畫布中的 Play 按鈕以運行 App,按住其中一行以彈出 Context Menu。選擇 Delete,你應該會看到所選餐廳從列表中被刪除。
讓我們繼續實作 Favorite 按鈕。點擊此按鈕後,App 將在所選餐廳中放一顆星。Restaurant
結構已經有一個名為 isFavorite
的屬性 (property),這個屬性會表示餐廳是否被標記為喜歡 (Favourite)。在默認情況下,其值設置為 false
。
struct Restaurant: Identifiable {
var id = UUID()
var name: String
var image: String
var isFavorite: Bool = false
}
與 Delete 功能相似,我們將在 ContentView
中創建一個單獨的函數,來設置喜歡的餐廳。插入以下程式碼以創建新功能:
private func setFavorite(item restaurant: Restaurant) {
if let index = self.restaurants.firstIndex(where: { $0.id == restaurant.id }) {
self.restaurants[index].isFavorite.toggle()
}
}
上面這段程式碼與 delete
函數非常相似。我們首先找出所選餐廳的索引,找到索引後,就可以更改其 isFavorite
屬性的值。在這裡,我們調用 toggle
函數來切換值。比如說,如果 isFavorite
的值原本是設置為 false
,在調用 toggle()
之後,該值將更改為 true
。
接下來,我們需要處理行的 UI。每當餐廳的 isFavorite
屬性設置為 true
時,該行應顯示一個星號指示符。像這樣更新 BasicImageRow
結構:
struct BasicImageRow: View {
var restaurant: Restaurant
var body: some View {
HStack {
Image(restaurant.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(restaurant.name)
if restaurant.isFavorite {
Spacer()
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
}
}
在上面的程式碼中,我們只在 HStack
中添加了一段程式碼。如果已選餐廳的 isFavorite
屬性設置為 true
,我們就在該行添加一個間隔符 (spacer) 和一個系統圖像 (system image)。
這樣我們就完成 Favorite 功能的實作了!最後,將以下這行程式碼插入到 // mark the selected restaurant as favorite
下面,以調用 setFavorite
函數:
self.setFavorite(item: restaurant)
現在可以測試 App 了!在畫布上執行 App,按住其中一行(例如 Petite Oyster),然後選擇 Favorite。你應該會看到該行尾部出現了一個星號。
這樣我們就可以在 SwiftUI 中實作 Context Menu!希望你喜歡本篇教學文章。我們即將出版有關 SwiftUI 的新書,請繼續關注我們的資訊!
你可以在 GitHub 下載完整項目以作參考。
原文:SwiftUI Tip: How to Create a Context Menu in iOS 13