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
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 Int
s.
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:
Declare a variable that can hold a closure in your tableview cell like
var buttonTappedAction : ((UITableViewCell) -> Void)?
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)
}
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")
}
}
Source: Stackoverflow.com