简体   繁体   中英

How to get the indexPath.row when I click a button on a table view cell?

这是我的应用程序

Hello the image above is the UI of my todo list app, now I just want to show the detail of item (First Item, second Item etc) when I click the detail button in the tableviewcell. So in order to get the property of the item, I need to know the indexPath of the row that I just clicked on the detail button.

I have tried some properties of the tableview like didSelectRowAt , or indexPathForSelectedRow , but both not work. For didSelectRowAt user need to click on the row first then click the detail button, and that's not what I want, and the indexPathForSelectedRow is not working for me.

A common, generalized solution for this type of problem is to connect the @IBAction of the button to a handler in the cell (not in the view controller), and then use a delegate-protocol pattern so the cell can tell the table when the button was tapped. The key is that when the cell does this, it will supply a reference to itself, which the view controller can then use to determine the appropriate indexPath (and thus the row).

For example:

  • Give your UITableViewCell subclass a protocol:

     protocol CustomCellDelegate: class { func cell(_ cell: CustomCell, didTap button: UIButton) } 
  • Hook up the @IBAction to the cell (not the view controller) and have that call the delegate method:

     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) } } 
  • Obviously, when the cell is created, call the configure method, passing, amongst other things, a reference to itself as the delegate:

     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 } } 
  • Finally, have the delegate method call indexPath(for:) to determine the index path for the cell in question:

     extension ViewController: CustomCellDelegate { func cell(_ cell: CustomCell, didTap button: UIButton) { guard let indexPath = tableView.indexPath(for: cell) else { return } // use `indexPath.row` here } } 

The other approach is to use closures, but again using the same general pattern of hooking the button @IBAction to the cell, but have it call a closure instead of the delegate method:

  • Define custom cell with closure that will be called when the button is tapped:

     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) } } 
  • When the table view data source creates the cell, supply a handler closure:

     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 } } 

I personally prefer the delegate-protocol pattern, as it tends to scale more nicely, but both approaches work.


Note, in both examples, I studiously avoided saving the indexPath in the cell, itself (or worse, “tag” values). By doing this, it protects you from getting misaligned if rows are later inserted and deleted from the table.


By the way, I used fairly generic method/closure names. In a real app, you might give them more meaningful names, eg, didTapInfoButton , didTapSaveButton , etc.) that clarifies the functional intent.

Implement the delegate method tableView(_:accessoryButtonTappedForRowWith:)

func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)

However if you want to navigate to a different controller connect a segue to the accessory view button

If the button is a custom button see my answer in Issue Detecting Button cellForRowAt

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM