利用 Identifiable 的資料判斷點選的 button 在哪個 cell
操作 iOS App 時,我們時常在表格的 cell 上點擊按鈕, 比方下圖 App Store App 的價錢按鈕。不過當使用者點擊價錢時,我們要如何從程式判斷他想購買哪個 App 呢 ?
判斷的方法很多,其中一招是利用轉換座標的 convert(_:to:)
function 判斷點選的 button 在第幾個 cell。
以下我們介紹另一個方法,此方法主要參考 Apple 的 Today App 範例,利用 Identifiable 的資料判斷點選的 button 在哪個 cell。
顯示 App 清單
在示範判斷的方法前,我們先快速做個顯示 App 清單的畫面。
- storyboard 畫面設計。
設定 auto layout,button 的 Horizontal Content Hugging 設為 252。
- 定義資料型別 App。
遵從 protocol Identifiable,讓 App 成為可用 id 識別區分的資料。
App.swift。
import Foundation
struct App: Identifiable {
let id = UUID()
let name: String
}
- 定義 AppTableViewCell & 連結 outlet。
import UIKit
class AppTableViewCell: UITableViewCell {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var iconImageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
- 定義 AppTableViewController。
class AppTableViewController: UITableViewController {
let apps = [
App(name: "IG"),
App(name: "LINE")
]
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return apps.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AppTableViewCell", for: indexPath) as! AppTableViewCell
let app = apps[indexPath.item]
cell.nameLabel.text = app.name
cell.iconImageView.image = UIImage(named: app.name)
return cell
}
}
App 執行的畫面如下。
利用 Identifiable 的資料判斷點選的 button 在哪個 cell
- 定義 buy button 的類別。
宣告儲存 App id 的 property id,之後我們將用它找出對應的 App。型別 App.ID 的 ID 是 Identifiable 的 associatedtype,它將等於 UUID,因為我們在 App 裡宣告的 id 型別是 UUID。
class BuyButton: UIButton {
var id: App.ID?
}
- 設定 buy button 的類別。
- 連結 buy button 的 outlet。
class AppTableViewCell: UITableViewCell {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var buyButton: BuyButton!
@IBOutlet weak var iconImageView: UIImageView!
- 設定 buy button 的 id。
將 buy button 的 id 設為 app 的 id。
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AppTableViewCell", for: indexPath) as! AppTableViewCell
let app = apps[indexPath.item]
cell.nameLabel.text = app.name
cell.iconImageView.image = UIImage(named: app.name)
cell.buyButton.id = app.id
return cell
}
- 定義找出成員在 array 裡 index 的 function。
function indexOfElement 接收的參數為成員的 id ,它將回傳成員在 array 裡的 index。
Array+Extensions.swift。
extension Array where Element: Identifiable {
func indexOfElement(with id: Element.ID) -> Self.Index {
guard let index = firstIndex(where: { $0.id == id }) else {
fatalError()
}
return index
}
}
- 定義 buy button 連結的 IBAction & 購買 App 的 function。
連結的方法有以下三種,以下我們示範兩種方法。
方法1: 連到 controller 的 class。
AppTableViewController.swift。
func buy(app: App) {
print("buy \(app.name)")
}
@IBAction func buyButtonTap(_ sender: BuyButton) {
guard let id = sender.id else { return }
let index = apps.indexOfElement(with: id)
let app = apps[index]
buy(app: app)
}
說明
guard let id = sender.id else { return }
從按鈕 sender 讀出 App 的 id。
let index = apps.indexOfElement(with: id)
利用 indexOfElement 找出 array 裡的 index。
let app = apps[index]
從 apps[index] 讀出對應的 app。
方法2: 連到 cell 的 class。
此做法比較複雜,cell 要另外搭配 delegate 跟 controller 溝通。
AppTableViewCell.swift。
import UIKit
protocol AppTableViewCellDelegate: AnyObject {
func buyButtonTap(sender: BuyButton)
}
class AppTableViewCell: UITableViewCell {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var buyButton: BuyButton!
@IBOutlet weak var iconImageView: UIImageView!
weak var delegate: AppTableViewCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
@IBAction func buyButtonTap(_ sender: BuyButton) {
delegate?.buyButtonTap(sender: sender)
}
}
點擊購買按鈕時,呼叫 delegate 的 buyButtonTap。
AppTableViewController.swift。
class AppTableViewController: UITableViewController {
let apps = [
App(name: "IG"),
App(name: "LINE")
]
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return apps.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AppTableViewCell", for: indexPath) as! AppTableViewCell
let app = apps[indexPath.item]
cell.nameLabel.text = app.name
cell.iconImageView.image = UIImage(named: app.name)
cell.buyButton.id = app.id
cell.delegate = self
return cell
}
func buy(app: App) {
print("buy \(app.name)")
}
}
定義 buy(app:),在 tableView(_:cellForRowAt:) 設定 cell 的 delegate 為 controller。
extension AppTableViewController: AppTableViewCellDelegate {
func buyButtonTap(sender: BuyButton) {
guard let id = sender.id else { return }
let index = apps.indexOfElement(with: id)
let app = apps[index]
buy(app: app)
}
}
遵從 AppTableViewCellDelegate,定義 buyButtonTap(sender:)。