[ios] UIButton action in table view cell

I am trying to run an action for a button being pressed within a table view cell. The following code is in my table view controller class.

The button has been described as "yes" in an outlet in my class of UITableViewCell called requestsCell.

I am using Parse to save data and would like to update an object when the button is pressed. My objectIds array works fine, the cell.yes.tag also prints the correct number to the logs, however, I cannot get that number into my "connected" function in order to run my query properly.

I need a way to get the indexPath.row of the cell to find the proper objectId.

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as requestsCell

    // Configure the cell...

    cell.name.text = requested[indexPath.row]

    imageFiles[indexPath.row].getDataInBackgroundWithBlock{
        (imageData: NSData!, error: NSError!) -> Void in

        if error == nil {

            let image = UIImage(data: imageData)

            cell.userImage.image = image
        }else{
            println("not working")
        }    
    }

    cell.yes.tag = indexPath.row
    cell.yes.targetForAction("connected", withSender: self)

    println(cell.yes.tag)

    return cell
}


func connected(sender: UIButton!) {

    var query = PFQuery(className:"Contacts")
    query.getObjectInBackgroundWithId(objectIDs[sender.tag]) {
        (gameScore: PFObject!, error: NSError!) -> Void in
        if error != nil {
            NSLog("%@", error)
        } else {
            gameScore["connected"] = "yes"
            gameScore.save()
        }
    }

}

This question is related to ios swift uitableview parsing

The answer is


in Swift 4

in cellForRowAt indexPath:

 cell.prescriptionButton.addTarget(self, action: Selector("onClicked:"), for: .touchUpInside)

function that run after user pressed button:

@objc func onClicked(sender: UIButton){
        let tag = sender.tag


    }

As Apple DOC

targetForAction:withSender:
Returns the target object that responds to an action.

You can't use that method to set target for UIButton.
Try addTarget(_:action:forControlEvents:) method


The accepted answer is good and simple approach but have limitation of information it can hold with tag. As sometime more information needed.

You can create a custom button and add properties as many as you like they will hold info you wanna pass:

class CustomButton: UIButton {
    var orderNo = -1
    var clientCreatedDate = Date(timeIntervalSince1970: 1)
}

Make button of this type in Storyboard or programmatically:

protocol OrderStatusDelegate: class {
    func orderStatusUpdated(orderNo: Int, createdDate: Date)
}

class OrdersCell: UITableViewCell {
    @IBOutlet weak var btnBottom: CustomButton!
    weak var delegate: OrderStatusDelegate?
}

While configuring the cell add values to these properties:

 func configureCell(order: OrderRealm, index: Int) {
     btnBottom.orderNo = Int(order.orderNo)
     btnBottom.clientCreatedDate = order.clientCreatedDate
 }

When tapped access those properties in button's action (within cell's subclass) that can be sent through delegate:

@IBAction func btnBumpTapped(_ sender: Any) {
    if let button = sender as? CustomButton {
        let orderNo = button.orderNo
        let createdDate = button.clientCreatedDate
        delegate?.orderStatusUpdated(orderNo: orderNo, createdDate: createdDate)
    }
}

With Swift 5 this is what, worked for me!!

Step 1. Created IBOutlet for UIButton in My CustomCell.swift

class ListProductCell: UITableViewCell {
@IBOutlet weak var productMapButton: UIButton!
//todo
}

Step 2. Added action method in CellForRowAtIndex method and provided method implementation in the same view controller

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ListProductCell") as! ListProductCell
cell.productMapButton.addTarget(self, action: #selector(ListViewController.onClickedMapButton(_:)), for: .touchUpInside)
return cell
    }

@objc func onClickedMapButton(_ sender: Any?) {

        print("Tapped")
    }

The accepted answer using button.tag as information carrier which button has actually been pressed is solid and widely accepted but rather limited since a tag can only hold Ints.

You can make use of Swift's awesome closure-capabilities to have greater flexibility and cleaner code.

I recommend this article: How to properly do buttons in table view cells using Swift closures by Jure Zove.

Applied to your problem:

  1. Declare a variable that can hold a closure in your tableview cell like

    var buttonTappedAction : ((UITableViewCell) -> Void)?
    
  2. Add an action when the button is pressed that only executes the closure. You did it programmatically with cell.yes.targetForAction("connected", withSender: self) but I would prefer an @IBAction outlet :-)

    @IBAction func buttonTap(sender: AnyObject) {
       tapAction?(self)
    }
    
  3. Now pass the content of func connected(sender: UIButton!) { ... } as a closure to cell.tapAction = {<closure content here...>}. Please refer to the article for a more precise explanation and please don't forget to break reference cycles when capturing variables from the environment.

We can create a closure for the button and use that in cellForRowAtIndexPath

class ClosureSleeve {
  let closure: () -> ()

  init(attachTo: AnyObject, closure: @escaping () -> ()) {
    self.closure = closure
    objc_setAssociatedObject(attachTo, "[\(arc4random())]", self,.OBJC_ASSOCIATION_RETAIN)
}

@objc func invoke() {
   closure()
 }
}

extension UIControl {
func addAction(for controlEvents: UIControlEvents = .primaryActionTriggered, action: @escaping () -> ()) {
  let sleeve = ClosureSleeve(attachTo: self, closure: action)
 addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
 }
}

And then in cellForRowAtIndexPath

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
    let cell = youtableview.dequeueReusableCellWithIdentifier(identifier) as? youCell
    cell?.selectionStyle = UITableViewCell.SelectionStyle.none//swift 4 style

      button.addAction {
       //Do whatever you want to do when the button is tapped here
        print("button pressed")
      }

    return cell
 }

Simple and easy way to detect button event and perform some action

class youCell: UITableViewCell
{
    var yourobj : (() -> Void)? = nil

    //You can pass any kind data also.
   //var user: ((String?) -> Void)? = nil

     override func awakeFromNib()
        {
        super.awakeFromNib()
        }

 @IBAction func btnAction(sender: UIButton)
    {
        if let btnAction = self.yourobj
        {
            btnAction()
          //  user!("pass string")
        }
    }
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    {
        let cell = youtableview.dequeueReusableCellWithIdentifier(identifier) as? youCell
        cell?.selectionStyle = UITableViewCellSelectionStyle.None

cell!. yourobj =
            {
                //Do whatever you want to do when the button is tapped here
                self.view.addSubview(self.someotherView)
        }

cell.user = { string in
            print(string)
        }

return cell

}

class TableViewCell: UITableViewCell {
   @IBOutlet weak var oneButton: UIButton!
   @IBOutlet weak var twoButton: UIButton!
}


class TableViewController: UITableViewController {

  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell

    cell.oneButton.addTarget(self, action: #selector(TableViewController.oneTapped(_:)), for: .touchUpInside)
    cell.twoButton.addTarget(self, action: #selector(TableViewController.twoTapped(_:)), for: .touchUpInside)

    return cell
}

func oneTapped(_ sender: Any?) {

    print("Tapped")
}

func twoTapped(_ sender: Any?) {

    print("Tapped")
   }
}

Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to swift

Make a VStack fill the width of the screen in SwiftUI Xcode 10.2.1 Command PhaseScriptExecution failed with a nonzero exit code Command CompileSwift failed with a nonzero exit code in Xcode 10 Convert Json string to Json object in Swift 4 iOS Swift - Get the Current Local Time and Date Timestamp Xcode 9 Swift Language Version (SWIFT_VERSION) How do I use Safe Area Layout programmatically? How can I use String substring in Swift 4? 'substring(to:)' is deprecated: Please use String slicing subscript with a 'partial range from' operator Safe Area of Xcode 9 The use of Swift 3 @objc inference in Swift 4 mode is deprecated?

Examples related to uitableview

Adding a UISegmentedControl to UITableView make UITableViewCell selectable only while editing How to fix Error: this class is not key value coding-compliant for the key tableView.' UITableView example for Swift Swift - how to make custom header for UITableView? How to insert new cell into UITableView in Swift How to detect tableView cell touched or clicked in swift Dynamic Height Issue for UITableView Cells (Swift) UIButton action in table view cell How to get the indexpath.row when an element is activated?

Examples related to parsing

Got a NumberFormatException while trying to parse a text file for objects Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) Python/Json:Expecting property name enclosed in double quotes Correctly Parsing JSON in Swift 3 How to get response as String using retrofit without using GSON or any other library in android UIButton action in table view cell "Expected BEGIN_OBJECT but was STRING at line 1 column 1" How to convert an XML file to nice pandas dataframe? How to extract multiple JSON objects from one file? How to sum digits of an integer in java?