#34 cell 的重覆利用 — to-do list 的完成打勾

不負責旅遊清單練習

作業來源:

涵蓋的部分:

基本版

1. 產生 array 資料(待辦事項,想讀的書,想去玩的國家等),顯示在表格裡。

2. 點選 cell 顯示 / 取消勾選。觀察表格上下捲動後,原本打勾的 cell 是否變沒打勾,原本沒打勾的 cell 是否變打勾。

3. 其他:點選灰色空心的愛心,會變成紅色實心的愛心,再次點選會變回灰色空心愛心。

練習目標:

Apple 程式會利用重複使用 cell,來增加運作時的效能,但當在表格上做註記(如打勾,愛心等)時,練習不因表格上下捲動而把註記位置給打亂

做法摘要:

GIF

產生資料:

為了減少產生資料花費的時間,使用 ChatGPT 產生資料,當產生的資料很怪或不是很滿意時,可以按下方中間 “Regenerate response” 重新產生答案,或是把問題打得更明確一些。

🌟 ChatGPT 相關教學

然後就是把資料複製到 Xcode 裡,加上兩個變數其型別為Bool去產生表格,
在 true 時顯示打勾或顯示愛心,false 時取消勾選或刪除愛心。

一樣先產生兩個 swift file 及 struct

最後產生的資料長這個樣子,兩個 Bool 設定為 false

var Countries: [Country] = [

Country(name: "JAPAN", attrction: [
Attraction(name: "東京塔", location: "東京都港區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "上野動物園", location: "東京都台東區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "富士山", location: "静岡縣", transportation: "巴士", visit: false, like: false),
Attraction(name: "淺草寺", location: "東京都台東區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "新宿", location: "東京都新宿區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "原宿", location: "東京都渋谷區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "日本橋", location: "東京都中央區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "東京迪士尼樂園", location: "千葉縣", transportation: "巴士", visit: false, like: false),
Attraction(name: "江戶村", location: "東京都墨田區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "秋葉原", location: "東京都千代田區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "銀座", location: "東京都中央區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "晴空塔", location: "東京都港區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "神田明神", location: "東京都千代田區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "涉谷", location: "東京都渋谷區", transportation: "地鐵", visit: false, like: false),
Attraction(name: "明治神宮", location: "東京都渋谷區", transportation: "地鐵", visit: false, like: false)]),

Country(name: "KOREA", attrction: [
Attraction(name: "首爾塔", location: "首爾市中心", transportation: "地鐵", visit: false, like: false),
Attraction(name: "景福宮", location: "景福門", transportation: "地鐵", visit: false, like: false),
Attraction(name: "光州塔", location: "光州市中心", transportation: "公車", visit: false, like: false),
Attraction(name: "首爾城堡", location: "首爾市中心", transportation: "地鐵", visit: false, like: false),
Attraction(name: "海邊藝術村", location: "釜山市西部海岸", transportation: "公車", visit: false, like: false),
Attraction(name: "秀川村", location: "忠清北道", transportation: "公車", visit: false, like: false),
Attraction(name: "漢江花園城", location: "首爾市東南部", transportation: "地鐵", visit: false, like: false),
Attraction(name: "東大門市場", location: "首爾市東部", transportation: "地鐵", visit: false, like: false),
Attraction(name: "釜山海洋世界", location: "釜山市西部海岸", transportation: "公車", visit: false, like: false),
Attraction(name: "慶州海灘", location: "慶州市", transportation: "公車", visit: false, like: false),
Attraction(name: "首爾科學博物館", location: "首爾市中心", transportation: "地鐵", visit: false, like: false),
Attraction(name: "松山文化村", location: "首爾市中心", transportation: "地鐵", visit: false, like: false),
Attraction(name: "南怡島", location: "釜山市西南部", transportation: "公車", visit: false, like: false),
Attraction(name: "韓國牛肉餐廳", location: "首爾市中心", transportation: "地鐵", visit: false, like: false),
Attraction(name: "濟州島", location: "忠清北道", transportation: "飛機", visit: false, like: false)
])

]

製作藍色勾勾:

藍色勾勾是利用 Table View Cell 裡的 accessory 內建的樣式,共有五種,這次只用到 none 跟 checkmark

當點選到特定 cell 時,希望可以觸發勾選或是取消打勾,但其實程式裡的變化是:
順序 1. 點選時先去改變 Bool 值 → 順序 2. Bool 值改變時再去改變顯示介面

所以在 cellForRow 的 function 裡(順序 2):
當變數 visit 的 Bool 值變成 true 則 accessoryType = .checkmark,變 false 則 accessoryType = .none

if Countries[indexPath.section].attrction[indexPath.row].visit == false {
cell.accessoryType = .none
} else {
cell.accessoryType = .checkmark
}

再來,設定點到 cell 時要讓 Bool 值改變(順序 1),
所以在 didSelectRow 的 function 裡,運用運算子! 對 Bool 值取相反值,

假設:A 是 Bool 型別,則觸發 A = !A (讀作:把相反 A 的值存入 A)時,
A 原本是 true 會變成 false,false 會變成 true。

在這裡程式會變這樣:

Countries[indexPath.section].attraction[indexPath.row].visit = !Countries[indexPath.section].attraction[indexPath.row].visit

🌟 因為 value type 的關係,得把它們(Countries & attraction)直接寫在一起。參考:

點選了 cell 改變了 Bool 值,最後,要再加上一行:
tableView.reloadData( ),去更新表格

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// !:Bool之間轉換,不是true就是false
Countries[indexPath.section].attrction[indexPath.row].visit = !Countries[indexPath.section].attrction[indexPath.row].visit
//Reloads the rows and sections of the table view.
tableView.reloadData()
}

製作點取變成紅色愛心:

愛心的部分則是使用 Button 搭配 SF Symbols,搭配顏色的變化

跟藍色勾勾觀念類似,在 cellForRow 的 function 裡:
當變數 like 的 Bool 值變成 true 則 heartButton.setImage 呈現紅色愛心,變 false 則呈現灰色空心愛心

這裡做了一個 enum 的 rawValue 字串對應 SF Symbols 的字串

enum Heart: String {
case like = "heart.fill"
case unlike = "heart"
}

搭配顏色變化

if Countries[indexPath.section].attrction[indexPath.row].like == false {
cell.heartButton.setImage(UIImage(systemName: "\(Heart.unlike.rawValue)"), for: .normal)
cell.heartButton.tintColor = .systemGray3
} else {
cell.heartButton.setImage(UIImage(systemName: "\(Heart.like.rawValue)"), for: .normal)
cell.heartButton.tintColor = UIColor.systemRed
}

一樣在最後,在按下愛心 Button 時,去改變 Bool 值,所以拉 IBAction func 到 Table View Controller,搭配 convert function 用座標判斷觸發的 cell 。

參考這篇

Button 的 IBAction func 程式,一樣也要輸入 tableView.reloadData( ) 去更新表格

@IBAction func heartButtonChosen(_ sender: UIButton) {

let point = sender.convert(CGPoint.zero, to: tableView)

if let indexPath = tableView.indexPathForRow(at: point) {
Countries[indexPath.section].attraction[indexPath.row].like = !Countries[indexPath.section].attraction[indexPath.row].like
}
tableView.reloadData()
}

在 cell 的 reuseIdentifier 使用 Extension 跟 computed property 去取代輸入ID字串

import Foundation
import UIKit

extension UITableViewCell {
static var reuseIdentifier: String {"\(Self.self)"}
}

cellForRow 程式碼

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: AttractionTableViewCell.reuseIdentifier, for: indexPath) as? AttractionTableViewCell else {
fatalError("dequeueReusableCell AttractionTableViewCell failed")
}

if Countries[indexPath.section].attraction[indexPath.row].visit == false {
cell.accessoryType = .none
} else {
cell.accessoryType = .checkmark
}

if Countries[indexPath.section].attraction[indexPath.row].like == false {
cell.heartButton.setImage(UIImage(systemName: "\(Heart.unlike.rawValue)"), for: .normal)
cell.heartButton.tintColor = .systemGray3
} else {
cell.heartButton.setImage(UIImage(systemName: "\(Heart.like.rawValue)"), for: .normal)
cell.heartButton.tintColor = UIColor.systemRed
}

configureCell(indexPath, cell)

return cell
}

細節調整:

在 Table View 的 Attributes Inspector 可以調整 Style,共有

🌟 Plain
🌟 Grouped
🌟 Inset Grouped

將在以下說明。

Plain

如文章上方的GIF所示,滑動時螢幕上方一直有一根 bar 在那裡。

Grouped

滑動時螢幕上方不會有 bar

Inset Grouped

把表格變成圓角的格式,記得表格跟 Table View 背景顏色要不一樣才看得出來。

--

--