iOS App 程式開發

SwiftUI 小技巧:在 iOS 13 實作 Context Menu 加強與設備的互動!

SwiftUI 小技巧:在 iOS 13 實作 Context Menu 加強與設備的互動!
SwiftUI 小技巧:在 iOS 13 實作 Context Menu 加強與設備的互動!
In: iOS App 程式開發, Swift 程式語言, SwiftUI 框架

較早之前,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!

注意:你需要有 Xcode 11 和 macOS Catalina (v10.15),才能遵循本教程。

在 SwiftUI 構建 Context Menu

首先,請下載初始專案,並在 Mac 上解壓縮。打開 SwiftUIList.xcodeproj 檔案,並運行專案或在畫布 (canvas) 中預覽,App 應該會顯示一個餐廳列表。

swiftui-context-menu-starter
編者註:如果你是 SwiftUI 新手,不知道如何建構列表,可以參閱我們的 SwiftUI 初學者教程

我們要做的,就為這個範例 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。

context-menu-delete-favorite

現在,讓我們繼續實作 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 下載完整項目以作參考。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。
原文SwiftUI Tip: How to Create a Context Menu in iOS 13
作者
Simon Ng
軟體工程師,AppCoda 創辦人。著有《iOS 17 App 程式設計實戰心法》、《iOS 17 App程式設計進階攻略》以及《精通SwiftUI》。曾任職於HSBC, FedEx等跨國企業,專責軟體開發、系統設計。2012年創立AppCoda技術部落格,定期發表iOS程式教學文章。現時專注發展AppCoda業務,致力於iOS程式教學、產品設計及開發。你可以到推特與我聯絡。
評論
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。