Issue Detecting Button cellForRowAt

Viewed 498

I need to detect if the button has been clicked in the UITableViewController

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

 let LikesBtn = cell.viewWithTag(7) as! UIButton

}
3 Answers

The easiest and most efficient way in Swift is a callback closure.

  • Subclass UITableViewCell, the viewWithTag way to identify UI elements is outdated.
  • Set the class of the custom cell to the name of the subclass and set the identifier to ButtonCellIdentifier in Interface Builder.

  • Add a callback property.

  • Add an action and connect the button to the action.

    class ButtonCell: UITableViewCell {
    
        var callback : (() -> Void)?
    
        @IBAction func buttonPressed(_ sender : UIButton) {
           callback?()
        }
    }
    
  • In cellForRow assign the callback to the custom cell.

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCellIdentifier", for: indexPath) as! ButtonCell
         cell.callback = {
             print("Button pressed", indexPath)  
         }
         return cell
      }
    
  • When the button is pressed the callback is called. The index path is captured.

Edit

There is a caveat if cells can be added or removed. In this case pass the UITableViewCell instance as parameter and get the index path from there

class ButtonCell: UITableViewCell {

    var callback : ((UITableViewCell) -> Void)?

    @IBAction func buttonPressed(_ sender : UIButton) {
       callback?(self)
    }
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCellIdentifier", for: indexPath) as! ButtonCell
     let item = dataSourceArray[indexPath.row]
     // do something with item
     cell.callback = { cell in
         let actualIndexPath = tableView.indexPath(for: cell)!
         print("Button pressed", actualIndexPath)  
     }
     return cell
  }

If even the section can change, well, then protocol/delegate may be more efficient.

First step: Make Subclass for your custom UITableViewCell, also register protocol.

Something like this:

protocol MyTableViewCellDelegate: class {
    func onButtonPressed(_ sender: UIButton, indexPath: IndexPath)
}

class MyTableViewCell: UITableViewCell {

    @IBOutlet var cellButton: UIButton!

    var cellIndexPath: IndexPath!
    weak var delegate: MyTableViewCellDelegate!


    override func awakeFromNib() {
        super.awakeFromNib()

        cellButton.addTarget(self, action: #selector(self.onButton(_:)), for: .touchUpInside)
    }

    func onButton(_ sender: UIButton) {
        delegate.onButtonPressed(sender, indexPath: cellIndexPath)
    }

}

In your TableViewController, make sure it conform to your just created protocol "MyTableViewCellDelegate".

Look at the code below for better understanding.

class MyTableViewController: UITableViewController, MyTableViewCellDelegate {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as? MyTableViewCell {

            cell.cellIndexPath = indexPath
            cell.delegate = self

            return cell
        } else {
            print("Something wrong. Check your cell idetifier or cell subclass")
            return UITableViewCell()
        }
    }

    func onButtonPressed(_ sender: UIButton, indexPath: IndexPath) {
        print("DID PRESSED BUTTON WITH TAG = \(sender.tag) AT INDEX PATH = \(indexPath)")
    }
}

Here is what I use:

First initialize the button as an Outlet and its action on your TableViewCell

class MainViewCell: UITableViewCell {

@IBOutlet weak var testButton: UIButton!

@IBAction func testBClicked(_ sender: UIButton) {

    let tag = sender.tag //with this you can get which button was clicked 

}



}

Then in you Main Controller in the cellForRow function just initialize the tag of the button like this:

class MainController: UIViewController, UITableViewDelegate, UITableViewDataSource, {

     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MainViewCell


        cell.testButton.tag = indexPath.row        

        return cell
    }

}
Related