#149 讓 table view 多選,而且每個 section 只能選一個 cell

最近有同學問到一個有趣的問題,如何讓 table view 可以多選,而且每個 section 只能選一個 cell。

比方我們想做一個高級西式餐廳點餐 App,點餐畫面如下。客人點餐時必須在各個分類裡各選一道菜。為了他的健康和荷包著想,怕他吃太多肉或花太多錢,我們限制他不能在一個分類裡選多道菜。

範例的餐點來自 Trine&Zen 崔妮傑恩微風信義店 menu,不過彼得潘還沒吃過

解答

接下來我們將從最基本的 table view 開始,一步步說明如何實現這樣的功能。

先實作單選的表格

MealCourse

定義餐點的資料型別,利用變數 data 儲存餐點的 array。

import Foundationenum MealCategory: String {
case soup
case mainCourse = "main course"
case dessert
}
struct MealCourse {
let category: MealCategory
let names: [String]
}
extension MealCourse {
static var data: [Self] {
[
MealCourse(category: .soup, names: [
"巴黎日安洋蔥湯",
"生蠔鮮蝦濃湯"
]),
MealCourse(category: .mainCourse, names: [
"爐烤美國菲力牛排",
"爐烤法式香料小羔羊",
"爐烤起司波士頓龍蝦"
]),
MealCourse(category: .dessert, names: [
"蘋果塔",
"熔岩巧克力",
"起士千層派"
]),
]
}
}

MealTableViewController

定義負責顯示餐點表格的 MealTableViewController。

import UIKitclass MealTableViewController: UITableViewController {    let mealCourses = MealCourse.data

override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return mealCourses.count
}


override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return mealCourses[section].names.count
}

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return mealCourses[section].category.rawValue
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MealCourseCell", for: indexPath)
let mealCourse = mealCourses[indexPath.section]
cell.textLabel?.text = mealCourse.names[indexPath.row]
return cell
}
}

storyboard 的畫面設定

執行 App

table view 完美地呈現餐點清單,不過 table view 預設是單選,所以客人很可憐,不能同時喝湯,吃肉,吃蛋糕。

將表格改成可以多選

將 table view 的 Selection 設為 Multiple Selection

  • 方法1: 從 Interface Builder 設定
  • 方法2: 從程式設定
tableView.allowsMultipleSelection = true

執行 App

Cool,現在我們可以多選了,可以同時喝湯,吃肉,吃蛋糕。

but 現在有個很大的問題,現在客人也能在一個 section 裡選多個 cell,所以他可以貪心地點三種不同的點心。為了客人的健康,我們一定要解決這個問題,讓一個 section 只能選一個 cell。

限制一個 section 只能選一個 cell

定義 table view delegate 的 function tableView(_:willSelectRowAt:),控制 cell 是否被選取。

override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if let selectIndexPathInSection = tableView.indexPathsForSelectedRows?.first(where: {
 $0.section == indexPath.section
}) {
tableView.deselectRow(at: selectIndexPathInSection, animated: false)
  }
return indexPath

}

當使用者點選 cell 時將觸發 tableView(_:willSelectRowAt:),回傳的 indexPath 會決定被選取的 cell。我們希望被點選的 cell 被選取,所以我們回傳 indexPath,因為參數 indexPath 代表目前被點選的 cell 位置。(ps: 若是回傳 nil,使用者點選的 cell 將不會被選取。)

不過除了選取 cell,我們還有一件重要的事要做。我們必須檢查使用者是否已選取 section 裡的其它 cell,然後呼叫 table view 的 deselectRow 取消選取,防止一個 section 出現多個被選取的 cell。

執行 App

Cool,現在 table view 可以多選,而且每個 section 只能選一個 cell,客人終於可以安心健康地享受大餐了。

One more thing,檢查每個 section 是否都有選取的 cell

為了實現真正的點餐功能,我們還要檢查每個 section 是否都有選取的 cell。就算你不喜歡喝湯,還是要選擇某一種湯,這樣廚師才不會難過。有興趣的朋友可進一步實現此功能。

範例 GitHub 連結

作品集

--

--

彼得潘的 iOS App Neverland
彼得潘的 100 道 Swift iOS App 謎題

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com