Общее, обобщенное решение для этого типа проблемы состоит в том, чтобы подключить @IBAction
кнопки к обработчику в ячейке (не в контроллере представления), а затем использовать шаблон протокола делегата, чтобы ячейка могла сказатьтаблица, когда кнопка была нажата.Ключ заключается в том, что когда ячейка делает это, она предоставляет ссылку на себя, которую контроллер представления затем может использовать для определения соответствующего indexPath (и, следовательно, строки).
Например:
Дайте вашему подклассу UITableViewCell
протокол:
protocol CustomCellDelegate: class {
func cell(_ cell: CustomCell, didTap button: UIButton)
}
Подключите @IBAction
к ячейке (не к контроллеру представления) и получитевызовите метод делегата:
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
@IBOutlet weak var customLabel: UILabel!
func configure(text: String, delegate: CustomCellDelegate) {
customLabel.text = text
self.delegate = delegate
}
@IBAction func didTapButton(_ button: UIButton) {
delegate?.cell(self, didTap: button)
}
}
Очевидно, что при создании ячейки вызовите метод configure
, передав, среди прочего, ссылку на себя в качестве делегата:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
let text = ...
cell.configure(text: text, delegate: self)
return cell
}
}
Наконец, вызовите метод делегата indexPath(for:)
, чтобы определить путь индекса для рассматриваемой ячейки:
extension ViewController: CustomCellDelegate {
func cell(_ cell: CustomCell, didTap button: UIButton) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
// use `indexPath.row` here
}
}
Другой подход заключается в использовании замыканий, но опять-таки с использованием того же общего шаблона для привязки кнопки @IBAction
к ячейке, но для вызова функции замыкания вместо метода делегата:
Определить пользовательскую ячейку с закрытием, которая будет вызываться, когда кнопка is постучал:
class CustomCell: UITableViewCell {
typealias ButtonHandler = (CustomCell) -> Void
var buttonHandler: ButtonHandler?
@IBOutlet weak var customLabel: UILabel!
func configure(text: String, buttonHandler: @escaping ButtonHandler) {
customLabel.text = text
self.buttonHandler = buttonHandler
}
@IBAction func didTapButton(_ button: UIButton) {
buttonHandler?(self)
}
}
Когда источник данных табличного представления создает ячейку, укажите закрытие обработчика:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
let text = ...
cell.configure(text: text, buttonHandler: { [weak self] cell in // the `[weak self]` is only needed if this closure references `self` somewhere
guard let indexPath = tableView.indexPath(for: cell) else { return }
// use `indexPath` here
})
return cell
}
}
Я личнопредпочитаю шаблон протокола делегата, так как он имеет тенденцию лучше масштабироваться, но оба подхода работают.
Обратите внимание, что в обоих примерах я старательно избегал сохранения indexPath
в самой ячейке (илихуже, «пометить» значения).Делая это, он защищает вас от смещения, если строки позже вставляются и удаляются из таблицы.
Кстати, я использовал довольно общие имена методов / замыканий.В реальном приложении вы можете дать им более значимые имена, например, didTapInfoButton
, didTapSaveButton
и т. Д.), Которые проясняют функциональные цели.