2

I have set up a button on the tableview cell and I am wondering how I can manipulate it. Namely, I am interested in changing the name title of the button and add another target (different capabilities of the button) to when the button is clicked. My thinking is that this needs to be added into the buttonClicked func, but I am not sure how to reference the specific cellForRow that was clicked. Perhaps a conditional can be used to in cellForRow that determines what the button title is? I am not too sure what the best way to go about this is.

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

    let button : UIButton = UIButton(type:UIButtonType.custom) as UIButton

    button.frame = CGRect(origin: CGPoint(x: 200,y :60), size: CGSize(width: 100, height: 24))
    let cellHeight: CGFloat = 44.0
    button.center = CGPoint(x: view.bounds.width / (4/3), y: cellHeight / 2.0)


    button.setTitleColor(.blue, for: .normal)
    button.addTarget(self, action: #selector(buttonClicked), for: UIControlEvents.touchUpInside)
    button.setTitle("Add", for: UIControlState.normal)

    cell.addSubview(button)


    return cell
}

func buttonClicked(sender : UIButton!) {
    print("Added!")


}
Kevin
  • 1,189
  • 2
  • 17
  • 44
  • 1
    This code will stack UIButtons in your cell every time the cell is dequeued. (Since a dequeue retrieves that that is no longer in use but not necessarily a new one so whatever it has is already there) – Pochi Aug 03 '17 at 02:53
  • @Pochi I am not quite sure what dequeue means and what it plays a role in tableviews, and when cells are dequeued, can you explain? How can I avoid the reuse of these cells? – Kevin Aug 03 '17 at 03:01
  • 2
    You don't avoid the reuse of the cells, thats a UITableView optimization. What you have to do is make your own subclass of UITableViewCell through the interface builder, then you can place your button connected to a property there. When you use the code above just cast it to your custom subclass using "as!" and access the button directly. Since you now have a custom subclass you can also add a property to identify the cell. This property has to be set on the above code as well, (simply saving the current index is fine), then you will be able to know which cell was pressed easily. – Pochi Aug 03 '17 at 03:04
  • Will the use of a subclass avoid the stacking of UIButtons? – Kevin Aug 03 '17 at 03:12
  • 1
    Yes it will avoid the UIButton stacking. – Pochi Aug 03 '17 at 03:18
  • I would suggest you use a delegation approach - https://stackoverflow.com/questions/28659845/swift-how-to-get-the-indexpath-row-when-a-button-in-a-cell-is-tapped/38941510#38941510 The cell should be the action target of the button and then the cell class can call the table view as a delegate and you can do what is appropriate based on the row – Paulw11 Aug 03 '17 at 03:50
  • @Paulw11 I've implemented the delegate approach you've recommended, thanks. How am I able to relate the row number to button qualities I am looking for? – Kevin Aug 03 '17 at 04:37
  • 1
    Your table view has some data model (typically an array) that is driving it; you would track the current state in this model and act accordingly – Paulw11 Aug 03 '17 at 04:38
  • I am not entirely sure what you mean by data model. Do you mean the values that are displayed in the tableView? – Kevin Aug 03 '17 at 04:45

4 Answers4

7

For Swift 4 and Swift 5

Find "indexPath.row" and "indexPath.section"

//Add target on button in "cellForRowAt"

cell.myBtn.addTarget(self, action: #selector(self.btnAction(_:)), for: .touchUpInside)

// Add function

func btnAction(_ sender: UIButton) {
    
    let point = sender.convert(CGPoint.zero, to: yourTableView as UIView)
    let indexPath: IndexPath! = yourTableView.indexPathForRow(at: point)
    
    print("row is = \(indexPath.row) && section is = \(indexPath.section)")
}
Bijender Singh Shekhawat
  • 3,934
  • 2
  • 30
  • 36
5

Chnage And Add With Your Code

    button.addTarget(self, action:#selector(YourViewController.buttonClicked(_:)), for: .touchUpInside)

    button.tag = indexPath.row



func buttonClicked(sender : UIButton!) {
    print("Added!")
     let row = sender.tag;
    print(row)

}
BHAVIK
  • 890
  • 7
  • 35
  • This is exactly what I do! The tag is equal to the row that the button is in and you can set it to a variable inside of the function or set it to a class-wide variable so that you can access the row in other functions. – Dan Levy Aug 03 '17 at 03:41
  • I don't quite get how I can use a tag. Are each tags associated with a different button qualities (function, title, etc). How can I associate these qualities with the tag? – Kevin Aug 03 '17 at 04:08
  • button.tag is store the value of indexpath.row so whenever you click on button then get tag value as indexpath.row so after that you can do your stuff – BHAVIK Aug 03 '17 at 04:13
  • on button click what you want so i will help you if any query? – BHAVIK Aug 03 '17 at 04:14
  • As a default, I want the button to say Add, but once it is clicked, I want it to say Subtract and another back to add, and have a function called for when the subtract is clicked. – Kevin Aug 03 '17 at 04:30
  • and what about section?? – Bijender Singh Shekhawat Nov 02 '17 at 09:15
2

There are two ways you can do it - the cheat's way or the proper way. The proper was is to subclass UITableViewCell, in which case you can hookup outlets in the normal way. The cheat's way is to use each element's tag property. If you set the tag of the button to (say) 1, you can use cell.viewWithTag within cellForRowAt to locate it: As in:

let button = cell.viewWithTag(1) as! UIButton

Be aware that it will search the whole hierarchy below cell, so you need to ensure you don't have multiple subviews with the same tag.

From your question, I'm guessing you want to click the button and have its title changed. The click handler should just reload the tableView row (or whole table if you're lazy like me), and cellForRowAt needs to know what title to show. This state of what to show on the button for each row must be held outside the tableView cell, since cells are reused as you scroll. This means that once the top row scrolls off the screen that same cell might be reused for the (say) 20th row. The code in cellForRowAt needs to completely reset the content of the cell in case it was already used for another row.

Adding some detail from your comment on the question, you never want to avoid reusing cells because this is a very good thing. You might be showing a table of 1,000 rows but only 10 are visible on screen. Reusing cells means iOS can create as few as 10 cells to show your entire table, rather than 1,000. Performance is faster as a result.

Michael
  • 8,891
  • 3
  • 29
  • 42
  • Ah I see what dequeueing does, thanks for that clarification. What are the differences between the two ways you mentioned besides how to implement them? – Kevin Aug 03 '17 at 03:10
  • The use of `tag` is a bit of a hack and prone to problems. For example, if you copy/paste your button, the new one will get the same tag (unless you remember to change it) and your code probably won't work as expected. Subclassing `UITableViewCell` allows you to put view-specific behaviour inside the view, which is the 'proper' way to do it. Having said that, I use the cheats way 80% of the time because it's much easier. The other 20% tends to be cases where I have multiple cell prototypes depending on row type, and it makes it much cleaner in `cellForRowAt`. – Michael Aug 04 '17 at 00:25
1

In Swift 4.x, you may simply have the TableView's delegate set a tag to it, like this:

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

    // Assign tags
    cell.myButton.tag = indexPath.row

    return cell
}

Then connect your button from the Storyboard by right-click + drag to your .swift file as an @IBAction, select Type -> UIButton and click Connect.

Here's the code:

 @IBAction func myButt(_ sender: UIButton) {
    let buttTag = sender.tag // get the Tag of the clicked button in the Cell

    // write the code that will be called on click of your button 
 }
Frank Eno
  • 2,581
  • 2
  • 31
  • 54