iOS 17 App 程式設計實戰心法(SwiftUI)

第 10 章
清單刪除、滑動動作、內容選單與動態控制器

If you spend too much time thinking about a thing, you'll never get it done. Make at least one definite move daily toward your goal.

– Bruce Lee

在上一章中,你學習如何處理列的選取,但列的刪除呢?我們如何從清單視圖中刪除一列呢?這是建立清單式 App 時的常見問題。「選取」、「刪除」、「插入」與「更新」是進行資料處理時的基本操作,我們已經介紹列的選取,因此在本章中將會關注於刪除。另外,我們將探索 FoodPin App 中加入的一些新功能:

  1. 新增使用者在表格列中水平滑動時的自訂動作按鈕,這通常在 SwiftUI中稱為「滑動」( Swipe )動作。
  2. 為 App新增社群分享功能,以方便使用者分享餐廳。

本章要學習的內容很多,但是它會很有趣且很值得。

執行列的刪除

讓我們進入編寫程式的部分,以了解我們如何從表格視圖中刪除一列。我們會繼續開發FoodPin App,如果你還沒有完成前一章的作業,你可以下載本章所準備的專案( http://www.appcoda.com/resources/swift59/swiftui-foodpin-oop.zip ),並新增「刪除」功能。

SwiftUI 讓開發者非常容易實作出「滑動刪除」的功能,它有一個名為「.onDelete」的內建修飾器,可將其加到 ForEach。在 RestaurantListView 結構中,修改 List 視圖的程式碼如下:

List {
    ForEach(restaurants.indices, id: \.self) { index in
        BasicTextImageRow(restaurant: $restaurants[index])
    }
    .onDelete(perform: { indexSet in
        restaurants.remove(atOffsets: indexSet)
    })

    .listRowSeparator(.hidden)
}
.listStyle(.plain)

這就是在清單視圖中啟用「滑動刪除」功能的方法。請注意,.onDelete 修飾器是加到 ForEach。

在 onDelete 的閉包中,它傳送給你一組用於刪除的索引,我們可以使用它來刪除資料集合中的紀錄。

看起來很簡單,對吧?我們來執行 App,並對其進行測試,如圖 10.1 所示。

圖 10.1. 滑動刪除
圖 10.1. 滑動刪除

你可以向左滑動來顯示「Delete」按鈕,這個按鈕是使用 .onDelete 修飾器時,由 iOS 自動產生的。你可以點擊「Delete」按鈕,或者持續滑動到左側邊緣,以刪除這個項目, 如圖 10.2 所示。

圖 10.2. 滑動刪除
圖 10.2. 滑動刪除

使用滑動動作

當你在系統的郵件 App 中向左滑動列時,你會看到「Trash」(垃圾)、「Flag」(旗標)、「More」(其他)等按鈕。「More」按鈕會帶出一個動作選單,提供諸如「Reply」(回覆)、「Flag」(旗標)等選項。如果你向右滑動,則會找到「Archive」(存檔)按鈕,如圖 10.3 所示。

圖 10.3. 在郵件 App 中的滑動動作
圖 10.3. 在郵件 App 中的滑動動作

SwiftUI 導入了一個名為「swipeActions」的修飾器,來讓開發者建立這類滑動動作,例如:如果我們想要加入「訂位」(Reserve a table )與「標記為最愛」(Mark as favorite )等兩個動作,當使用者向右滑動列時,我們可以將 swipeActions 修飾器應用於 BasicTextImageRow:

BasicTextImageRow(restaurant: $restaurants[index])
    .swipeActions(edge: .leading, allowsFullSwipe: false) {
        Button {

        } label: {
            Image(systemName: "heart")
        }
        .tint(.green)

        Button {

        } label: {
            Image(systemName: "square.and.arrow.up")
        }
        .tint(.orange)
    }

swipeAction 修飾器可以讓你指定滑動動作是否應顯示在前緣( leading )或後緣( trailing ),而在這個範例中,我們將其設定為在前緣顯示。在 content 參數中,我們加入兩個按鈕,以用來示範,當你在預覽中執行 App 時,向右滑動清單中的任何一列,則應該會顯示這兩個動作按鈕,如圖 10.4 所示。

圖 10.4. 加入滑動動作
圖 10.4. 加入滑動動作

除了滑動動作之外,還有另一種顯示選單的方式,我們來檢查並刪除 swipeActions 修飾器。

建立內容選單

我們可在內容選單(Context Menu)中顯示動作,而不是使用滑動動作。在 iOS 中, 使用者通常可以長按清單中的一列來帶出內容選單,如圖 10.5 所示。與滑動動作相似, SwiftUI 讓建立內容選單變得非常簡單,你只需將contextMenu 容器應用到視圖,並設定它的選單項目即可。

在 BasicTextImageView 中,刪除 .onTapGesture 與 .confirmationDialog 修飾器,然後將 .contextMenu 修飾器加到 HStack 視圖:

HStack {
  .
  .
  .
}
.contextMenu {

    Button(action: {
        self.showError.toggle()
    }) {
        HStack {
            Text("Reserve a table")
            Image(systemName: "phone")
        }
    }

    Button(action: {
        self.restaurant.isFavorite.toggle()
    }) {
        HStack {
            Text(restaurant.isFavorite ? "Remove from favorites" : "Mark as favorite")
            Image(systemName: "heart")
        }
    }
}
.alert("Not yet available", isPresented: $showError) {
  .
  .
  .
}

在 contextMenu 中,我們建立了兩個按鈕,一個是「Reserve a table」(訂位)按鈕,另一個是「Mark as favorite」(標記為最愛)按鈕。contextMenu 修飾器的閉包內的按鈕順序則決定了它在內容選單中的顯示順序。

這就是顯示內容選單所需的所有程式碼。在模擬器或預覽窗格中執行 App,然後長按任何一間餐廳,將會顯示內容選單。

圖 10.5. 長按一列來顯示內容選單
圖 10.5. 長按一列來顯示內容選單

SF Symbols介紹

SF Symbols 擁有 5,000 多個標誌,是一個圖示庫,旨在無縫整合 Apple 平台的系統字型 San Francisco。每個標誌有 9 種粗細與 3 種比例,可以自動與文字標籤對齊,它們可以使用向量圖形編輯工具匯出及編輯,以建立具有共享設計特點與輔助功能的自訂標誌。SF Symbols 5 引入一系列富有表現力的動畫、700 多個新標誌,以及自訂標誌的強化工具。

在討論如何使用動態控制器之前,我們先來討論之前在內容選單中使用的系統圖片的來源。這些圖示是從哪裡來的呢?你可能知道,你可以在 App 中提供自己的圖片,但是從 iOS 13 開始,Apple 導入一個名為「SF Symbols」的系統圖片綜合集合,這些標誌可讓開發者在任何 iOS App 中使用。隨著 Xcode 15 的發布,Apple 推出包含更多可配置的標誌並支援動畫的 SF Symbols 5。

這些圖片被稱為「標誌」,由於它整合了內建的 San Francisco 字型,因此要使用這些標誌,不需要額外的安裝,只要你的 App 是部署在執行 iOS 13(或更高版本)的裝置,你就可以直接取得這些標誌。

要使用這些標誌,你所需要做的是確定你要使用的標誌名稱。Apple 開發了一個名為「SF Symbols 5」( SF Symbols 5 (https://devimages-cdn.apple.com/design/resources/download/SF-Symbols-5.dmg) t)的 App,提供超過 5,000 個標誌供你使用,可讓你方便探索標誌,並找到適合你要求的標誌,我強烈建議你在進行下一節之前,先安裝這個 App。

圖 10.6. SF Symbols App
圖 10.6. SF Symbols App

當你找到標誌的名稱後,你可以使用下列程式碼來顯示圖片:

Image(systemName: "phone")

運用動態控制器

本章的目標之一是向你展示如何加入一個「分享」( Share )功能,來讓使用者分享他們最愛的餐廳。圖 10.7 顯示了 iOS 上的動態視圖範例,透過使用動態視圖,使用者可以輕鬆複製餐廳資訊,並將其貼到其他的 App 中(例如:Messages )。

圖 10.7. 複製所選的餐廳並貼到 Messages App 中
圖 10.7. 複製所選的餐廳並貼到 Messages App 中

UIKit 中,有一個名為「UIActivityViewController」的標準視圖控制器,它提供各種標準服務,例如:複製項目到剪貼簿、在社群媒體網站上分享內容、透過 Messages 傳送項目等,不過可惜的是,目前版本的 SwiftUI 仍然沒有加入這個原生元件。

每當 SwiftUI 中缺少任何元件時,我們總是可以從UIKit 框架中借用它。你可以透過建立遵循UIViewRepresentable 與 UIViewControllerRepresentable 協定的型別,將 UIKit 視圖與視圖控制器整合至SwiftUI 視圖中。

在專案導覽器中的「View」群組上按右鍵,並選擇「New File...」,然後選取「Swift File」模板,將檔案命名為「ActivityView.swift」。建立後,將其內容替換為:

import SwiftUI

struct ActivityView: UIViewControllerRepresentable {

    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil

    func makeUIViewController(context: Context) -> some UIViewController {
        let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
        return activityController
    }

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {

    }
}

為了在 UIKit 中使用 UIActivityViewController,我們建立了一個名為「ActivityView」的新型別,它遵循 UIViewControllerRepresentable 協定,此協定要求 makeUIViewController 與 updateUIViewController 方法的實作。在 makeUIViewController 方法中,我們使用所需的動態項目(Activity Items)及應用程式動態(Application Activities)來實例化 UIActivityViewController 的實例。

現在,你已經可以準備好在 SwiftUI App 中使用 ActivityView 了,我們將在 Restaurant ListView.swift 的內容選單中加入另一個項目來顯示動態視圖。切換到 RestaurantListView. swift,並在 BasicTextImageRow 的內容選單中插入新按鈕:

Button(action: {
    self.showOptions.toggle()
}) {
    HStack {
        Text("Share")
        Image(systemName: "square.and.arrow.up")
    }
}

要顯示動態視圖,則將 .sheet 修飾器加到 HStack 視圖:

.sheet(isPresented: $showOptions) {

    let defaultText = "Just checking in at \(restaurant.name)"

    if let imageToShare = UIImage(named: restaurant.image) {
        ActivityView(activityItems: [defaultText, imageToShare])
    } else {
        ActivityView(activityItems: [defaultText])
    }
}

.sheet 修飾器監看showOptions 的變化,當它設定為「true」時,App 會帶出動態視圖控制器。我們使用訊息及所選的餐廳圖片來實例化動態視圖。

如果你執行 App 並在內容選單中選擇「Share」選項,你將看到動態視圖,如圖 10.8 所示。在動態視圖中,你可以選擇「Copy」來複製預設文字,然後你可以將其貼到 iMessage 等 App 中。

圖 10.8. 顯示動態視圖
圖 10.8. 顯示動態視圖

你無法使用模擬器來測試社群分享, 但是如果你將App 部署到安裝了Twitter(已經更名為 X ) 或Facebook 的實機上,你會在動態視圖中找到這些選項。

本章小結

在本章中,我示範了如何在表格視圖中處理刪除,並教你如何在清單中建立滑動動作。此外,你還學習了如何建立內容選單,並使用 UIActivityViewController 來實作「分享」功能。FoodPin App 正持續地改進中,你應該對目前取得的成果感到自豪才是。

在本章所準備的範例檔中,有最後完整的專案(http://www.appcoda.com/resources/swift59/swiftui-foodpin-list-deletion.zip )可供你下載參考。在下一章中,我們將了解一些新內容,並建立一個導覽控制器。