Reputation: 465
Hi I have a custom UITableViewCell with three buttons to handle a shopping cart function, Plus,Minus and Delete button and I need to know which cell has been touched.
I've already tried to use the "tag solution" but it isn't working due to the lifecycle of the cells.
Can anyone please help me find a solution?
Thanks in advance
Upvotes: 22
Views: 56819
Reputation: 923
Swift 5
First thing create the protocol
(delegate).
protocol CustomDelegate: AnyObject{
func didSelectedTheButton(_ index: Int)
}
Create the UITableViewCell
and initialize the protocol
as instance. And add button and button action in your TableViewCell. Use the protocol (delegate) in the button Action. Please find the below codes for reference
class TableViewCell: UITableViewCell {
lazy var button: UIButton! = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .darkGray
button.setTitleColor(.white, for: .normal)
button.addTarget(self, action: #selector(didTaped(_:)), for: .touchUpInside)
button.tag = 1
button.setTitle("Tap", for: .normal)
return button
}()
weak var delegate: CustomDelegate?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.backgroundColor = .clear
self.backgroundColor = .clear
self.contentView.addSubview(button)
self.button.heightAnchor.constraint(equalToConstant: 60).isActive = true
self.button.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width * 0.66).isActive = true
self.button.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor).isActive = true
self.button.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func didTaped(_ sender: UIButton){
//Call the protocol function for receive the action in ViewController
delegate?.didSelectedTheButton(sender.tag)
}
}
Next create the UIViewController
and integrate the UITableView
in your ViewController. In function tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
assign your viewcontrollor to delegate which initialise in UITableViewCell
class SecondViewController: UIViewController {
lazy var tableView: UITableView! = {
let tableView = UITableView(frame: .zero, style: .grouped)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(TableViewCell.self, forCellReuseIdentifier: String(describing: TableViewCell.self))
tableView.backgroundColor = .white
tableView.separatorColor = .clear
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
//
self.view.addSubview(self.tableView)
NSLayoutConstraint.activate([
self.tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
self.tableView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
self.tableView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),
self.tableView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
])
}
}
extension SecondViewController: UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TableViewCell.self), for: indexPath) as! TableViewCell
cell.button.tag = indexPath.row
//ViewController Assign for protocol delegate
cell.delegate = self
cell.button.setTitle("Button \(indexPath.row)", for: .normal)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
extension SecondViewController: CustomDelegate{
//Received the button action for cell
/**
index - contains row value for button selected
*/
func didSelectedTheButton(_ index: Int) {
// Enter your code here what did you want when the tableViewCell button action
print(index)
}
}
Upvotes: 0
Reputation: 1
if you are creating table view cell programmatically you need to add add subview to contentView not cell itself
Upvotes: 0
Reputation: 289
swift 4.2
You can also use closures instead of delegates
1) In your UITableViewCell :
class ExampleCell: UITableViewCell {
//create your closure here
var buttonPressed : (() -> ()) = {}
@IBAction func buttonAction(_ sender: UIButton) {
//Call your closure here
buttonPressed()
}
}
2) In your ViewController
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExampleCell", for: indexPath) as! ExampleCell
cell.buttonPressed = {
//Code
}
return cell
}
}
Upvotes: 26
Reputation: 4815
UIButton in Tableview cell. Programically create action method in swift 4.2
cell.btnlike.addTarget(self, action: #selector(buttonbtnlikePressed(_:event:)), for: .touchUpInside)
@objc func buttonbtnlikePressed(_ sender: Any, event: Any) {
let point : CGPoint = (sender as AnyObject).convert(CGPoint.zero, to:tblPost)
var indexPath = self.tblPost!.indexPathForRow(at: point)
if let btnlike = sender as? UIButton{
if btnlike.isSelected{
btnlike.isSelected = false
}else{
btnlike.isSelected = true
}
}
}
Upvotes: 1
Reputation: 12910
I was resolving this using a cell delegate method within UITableViewCell's subclass.
Quick overview:
1) Create a protocol
protocol YourCellDelegate : class {
func didPressButton(_ tag: Int)
}
2) Subclass your UITableViewCell
(if you haven't done so):
class YourCell : UITableViewCell
{
var cellDelegate: YourCellDelegate?
@IBOutlet weak var btn: UIButton!
// connect the button from your cell with this method
@IBAction func buttonPressed(_ sender: UIButton) {
cellDelegate?.didPressButton(sender.tag)
}
...
}
3) Let your view controller conform to YourCellDelegate
protocol that was implemented above.
class YourViewController: ..., YourCellDelegate { ... }
4) Set a delegate, after the cell has been defined (for reusing).
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! YourCell
cell.cellDelegate = self
cell.btn.tag = indexPath.row
5) In the same controller (where is your implemented UITableView delegate/datasource), put a method from YourCellDelegate
protocol.
func didPressButton(_ tag: Int) {
print("I have pressed a button with a tag: \(tag)")
}
Now, your solution is not tag / number dependent. You can add as many buttons as you want, so you are ready to get response via delegate regardless how many buttons you want to install.
This protocol-delegate solution is preferred in iOS logic and it can be used for other elements in table cell, like UISwitch
, UIStepper
, and so on.
Upvotes: 86
Reputation: 1679
@pedrouan is great, except using button's tag
option. In many cases, when you set button on tableViewCell
, those buttons will modify tableView dataSource.(e.g. InsertRow, DeleteRow).
But the tag of the button is not updated even if a new cell is inserted
or deleted
. Therefore, it is better to pass the cell
itself as a parameter rather than passing the button's tag
to the parameter.
Here is my example to achieve this.
Your ExampleCell
protocol ExampleCellDelegate: class {
func didTapButton(cell: ExampleCell)
}
class ExampleCell: UITableViewCell {
weak var cellDelegate: ExampleCellDelegate?
@IBAction func btnTapped(_ sender: UIButton) {
cellDelegate?.didTapButton(cell: self)
}
}
Your ViewController
class ViewController: ExampleCellDelegate {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "ExampleCell", for: indexPath) as? ExampleCell {
cell.cellDelegate = self
return cell
}
return UITableViewCell()
}
func didTapButton(cell: ExampleCell) {
if let indexPath = tableView.indexPath(for: cell) {
// do Something
}
}
}
Upvotes: 11
Reputation: 3395
SWIFT 4.*
It can be done like following way too, Not required much coding and delegation, Simple and easy.
Put following code in cellForItemAt
for UICollectionView
or in cellForRowAt
for UITableView
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: #selector(buttonSelected), for: .touchUpInside)
And your Method will be
@objc func buttonSelected(sender: UIButton){
print(sender.tag)
}
Thats all.
Upvotes: 15
Reputation: 203
I came across the same problem after making the IBOutlets private as has been broadly suggested by the community.
Here is my solution:
< In your cell class >
protocol YourCellDelegate: class {
func didTapButton(_ sender: UIButton)
}
class YourCell: UITableViewCell {
weak var delegate: YourCellDelegate?
@IBAction func buttonTapped(_ sender: UIButton) {
delegate?.didTapButton(sender)
}
}
< In your ViewController >
class ViewController: UIViewController, YourCellDelegate {
func didTapButton(_ sender: UIButton) {
if let indexPath = getCurrentCellIndexPath(sender) {
item = items[indexPath.row]
}
}
func getCurrentCellIndexPath(_ sender: UIButton) -> IndexPath? {
let buttonPosition = sender.convert(CGPoint.zero, to: tableView)
if let indexPath: IndexPath = tableView.indexPathForRow(at: buttonPosition) {
return indexPath
}
return nil
}
}
Upvotes: 19
Reputation: 8322
You can also get selected button index using tableViewCell view's hierarchy.
Using following steps :
add selector to the cellForRowAtIndexpath of tableview :
btn?.addTarget(self, action:#selector(buttonPressed(_:)), for:.touchUpInside)
2 . get indexPath using following method :
func buttonPressed(_ sender: AnyObject) {
let button = sender as? UIButton
let cell = button?.superview?.superview as? UITableViewCell
let indexPath = tblview.indexPath(for: cell!)
print(indexPath?.row)
}
Upvotes: 3