detecting uibutton pressed in tableview: Swift Best Practices

I have a tableview with a variable number of cells representing students that correspond to their particular instructor. They are custom cells with a button that triggers a segue to a new VC, bringing up detailed information on the student whose cell it was. My question is:

What is the best practice in swift for identifying which button was pressed?

Once i know the index path, I can identify which student's information needs to be passed to the next VC. There is a great answer for objective C in the post below, but I'm not sure how to translate to Swift. Any help would be much appreciated.

Detecting which UIButton was pressed in a UITableView


If your code allows, I'd recommend you set the UIButton tag equal to the indexPath.row, so when its action is triggered, you can pull the tag and thus row out of the button data during the triggered method. For example, in cellForRowAtIndexPath you can set the tag:

button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)

then in buttonClicked:, you can fetch the tag and thus the row:

func buttonClicked(sender:UIButton) {

    let buttonRow = sender.tag
}

Otherwise, if that isn't conducive to your code for some reason, the Swift translation of this Objective-C answer you linked to:

- (void)checkButtonTapped:(id)sender
{
    CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     ...
    }
}

is:

func checkButtonTapped(sender:AnyObject) {
      let buttonPosition = sender.convert(CGPoint.zero, to: self.tableView)
    let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
    if indexPath != nil {
        ...
    }
}

Swift 3.0 Solution

cell.btnRequest.tag = indexPath.row

    cell.btnRequest.addTarget(self,action:#selector(buttonClicked(sender:)), for: .touchUpInside)

    func buttonClicked(sender:UIButton) {

            let buttonRow = sender.tag
        }

Updated for Swift 3

If the only thing you want to do is trigger a segue on a touch, it would be against best practice to do so via a UIButton. You can simply use UIKit's built in handler for selecting a cell, i.e. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath). You could implement it doing something like the following:

Create a custom UITableViewCell

class StudentCell: UITableViewCell {
    // Declare properties you need for a student in a custom cell.
    var student: SuperSpecialStudentObject!

    // Other code here...
}

When you load your UITableView, pass the data into the cell from you data model:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "StudentCell", for: indexPath) as! StudentCell
    cell.student = superSpecialDataSource[indexPath.row]
    return cell
}

Then use didSelectRow atIndexPath to detect when a cell has been selected, access the cell and it's data, and pass the value in as a parameter to performSegue.

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRow(at: indexPath) as! StudentCell

    if let dataToSend = cell.student {
        performSegue(withIdentifier: "DestinationView", sender: dataToSend)
    }
}

And finally in prepareForSegue:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "DestinationView" {
        let destination = segue.destination as! DestinationViewController
        if let dataToSend = sender as? SuperSpecialStudentObject {
            destination.student = dataToSend
        }
    }
}

Alternatively if you want them to only select a part of the cell instead of when they touch anywhere inside the cell, you could add an accessory item onto your cell such as the detail accessory item (looks like the circle with an "i" inside of it) and use override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) instead.


Another possible solution would be using dispatch_block_t. If you do it with Storyboard you first have to create a member variable in your custom UITableViewCell class.

var tapBlock: dispatch_block_t?

Then you have to create an IBAction and call the tapBlock.

@IBAction func didTouchButton(sender: AnyObject) {
    if let tapBlock = self.tapBlock {
        tapBlock()
    }
}

In your view controller with the UITableView you can simply react to the button events like this

let cell = tableView.dequeueReusableCellWithIdentifier("YourCellIdentifier", forIndexPath: indexPath) as! YourCustomTableViewCell

cell.tapBlock = {
   println("Button tapped")
}

However you have to be aware when accessing self inside the block, to not create a retain cycle. Be sure to access it as [weak self].