How to add a button with click event on UITableViewCell in Swift? - ios

In my main page, I created a xib file for UITableViewCell. I'm loading the cell from that xib file and its working fine.
Inside of the cell I have some labels and buttons. I'm aiming to change the label by clicking to the button on the cell.
My Code likes below
import UIKit
class SepetCell: UITableViewCell{
#IBOutlet var barcode: UILabel!
#IBOutlet var name: UILabel!
#IBOutlet var fav: UIButton!
#IBOutlet var strep: UIStepper!
#IBOutlet var times: UILabel!
#IBAction func favoriteClicked(sender: UIButton) {
println(sender.tag)
println(times.text)
SepetViewController().favorite(sender.tag)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
This is my xib files behind codes as .swift.
The codes in the main page likes below:
import UIKit
import CoreData
class SepetViewController: UIViewController, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate {
#
IBOutlet
var sepetTable: UITableView!
var barcodes: [CART] = []
let managedObjectContext = (UIApplication.sharedApplication().delegate as!AppDelegate).managedObjectContext
override func viewWillAppear(animated: Bool) {
if let moc = self.managedObjectContext {
var nib = UINib(nibName: "SepetTableCell", bundle: nil)
self.sepetTable.registerNib(nib, forCellReuseIdentifier: "productCell")
}
fetchLog()
sepetTable.reloadData()
}
func fetchLog() {
if let moc = self.managedObjectContext {
barcodes = CART.getElements(moc);
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) - > Int {
return self.barcodes.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) - > UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("productCell") as ? SepetCell
if cell == nil {
println("cell nil")
}
let product: CART
product = barcodes[indexPath.row]
cell!.barcode ? .text = product.barcode
cell!.name ? .text = product.name
cell!.fav.tag = indexPath.row
return cell!
}
func favorite(tag: Int) {
}
}
When i clicked fav button inside of the Cell. I wanted to change times label text to anything for example.
When I clicked to the fav button, the event will gone to the SepetCell.swift favoriteClicked(sender: UIButton) function.
So if i try to call:
SepetViewController().favorite(sender.tag)
It will go inside of the
func favorite(tag: Int) {
sepetTable.reloadData()
}
but sepetTable is nil when it is gone there. I think it is because of when I call this SepetViewController().favorite(sender.tag) function. It firstly creates SepetViewController class. So because of object is not setted it is getting null.
How can I reach that sepetTable or what is the best way to solve this issue.
Thanks.

Popular patterns for solving this problem are closures and delegates.
If you want to use closures, you would do something like this:
final class MyCell: UITableViewCell {
var actionBlock: (() -> Void)? = nil
then
#IBAction func didTapButton(sender: UIButton) {
actionBlock?()
}
then in your tableview delegate:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) - > UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellIdentifier") as? MyCell
cell?.actionBlock = {
//Do whatever you want to do when the button is tapped here
}
A popular alternative is to use the delegate pattern:
protocol MyCellDelegate: class {
func didTapButtonInCell(_ cell: MyCell)
}
final class MyCell: UITableViewCell {
weak var delegate: MyCellDelegate?
then
#IBAction func didTapButton(sender: UIButton) {
delegate?.didTapButtonInCell(self)
}
..
Now in your view controller:
then in your tableview delegate:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) - > UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellIdentifier") as? MyCell
cell?.delegate = self
And add conformance to the protocol like this:
extension MyViewController: MyCellDelegate {
didTapButtonInCell(_ cell: MyCell) {
//Do whatever you want to do when the button is tapped here
}
}
Hope this helps!

All patterns above are fine.
my two cents, in case You add by code (for example multiple different cells and so on..)
there is a FAR simple solution.
As buttons allow to specify a "target" You can pass directly the controller AND action to cell/button when setting it.
In controller:
let selector = #selector(self.myBtnAction)
setupCellWith(target: self, selector: selector)
...
in custom cell with button:
final func setupCellWith(target: Any? selector: Selector){
btn.addTarget(target,
action: selector,
for: .touchUpInside)
}

Add target for that button.
button.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
Set tag of that button since you are using it.
button.tag = indexPath.row
Achieve this by subclassing UITableViewCell. button on that cell, connect it via outlet.
To get the tag in the connected function:
#objc func connected(sender: UIButton){
let buttonTag = sender.tag
}

2 am answer: You're over thinking this. Create a custom TableViewCell class; set the prototype cell class to your new custom class; and then create an IBAction.

Related

add a button to a custom cell that displays another modal view controller [duplicate]

In my main page, I created a xib file for UITableViewCell. I'm loading the cell from that xib file and its working fine.
Inside of the cell I have some labels and buttons. I'm aiming to change the label by clicking to the button on the cell.
My Code likes below
import UIKit
class SepetCell: UITableViewCell{
#IBOutlet var barcode: UILabel!
#IBOutlet var name: UILabel!
#IBOutlet var fav: UIButton!
#IBOutlet var strep: UIStepper!
#IBOutlet var times: UILabel!
#IBAction func favoriteClicked(sender: UIButton) {
println(sender.tag)
println(times.text)
SepetViewController().favorite(sender.tag)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
This is my xib files behind codes as .swift.
The codes in the main page likes below:
import UIKit
import CoreData
class SepetViewController: UIViewController, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate {
#
IBOutlet
var sepetTable: UITableView!
var barcodes: [CART] = []
let managedObjectContext = (UIApplication.sharedApplication().delegate as!AppDelegate).managedObjectContext
override func viewWillAppear(animated: Bool) {
if let moc = self.managedObjectContext {
var nib = UINib(nibName: "SepetTableCell", bundle: nil)
self.sepetTable.registerNib(nib, forCellReuseIdentifier: "productCell")
}
fetchLog()
sepetTable.reloadData()
}
func fetchLog() {
if let moc = self.managedObjectContext {
barcodes = CART.getElements(moc);
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) - > Int {
return self.barcodes.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) - > UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("productCell") as ? SepetCell
if cell == nil {
println("cell nil")
}
let product: CART
product = barcodes[indexPath.row]
cell!.barcode ? .text = product.barcode
cell!.name ? .text = product.name
cell!.fav.tag = indexPath.row
return cell!
}
func favorite(tag: Int) {
}
}
When i clicked fav button inside of the Cell. I wanted to change times label text to anything for example.
When I clicked to the fav button, the event will gone to the SepetCell.swift favoriteClicked(sender: UIButton) function.
So if i try to call:
SepetViewController().favorite(sender.tag)
It will go inside of the
func favorite(tag: Int) {
sepetTable.reloadData()
}
but sepetTable is nil when it is gone there. I think it is because of when I call this SepetViewController().favorite(sender.tag) function. It firstly creates SepetViewController class. So because of object is not setted it is getting null.
How can I reach that sepetTable or what is the best way to solve this issue.
Thanks.
Popular patterns for solving this problem are closures and delegates.
If you want to use closures, you would do something like this:
final class MyCell: UITableViewCell {
var actionBlock: (() -> Void)? = nil
then
#IBAction func didTapButton(sender: UIButton) {
actionBlock?()
}
then in your tableview delegate:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) - > UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellIdentifier") as? MyCell
cell?.actionBlock = {
//Do whatever you want to do when the button is tapped here
}
A popular alternative is to use the delegate pattern:
protocol MyCellDelegate: class {
func didTapButtonInCell(_ cell: MyCell)
}
final class MyCell: UITableViewCell {
weak var delegate: MyCellDelegate?
then
#IBAction func didTapButton(sender: UIButton) {
delegate?.didTapButtonInCell(self)
}
..
Now in your view controller:
then in your tableview delegate:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) - > UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellIdentifier") as? MyCell
cell?.delegate = self
And add conformance to the protocol like this:
extension MyViewController: MyCellDelegate {
didTapButtonInCell(_ cell: MyCell) {
//Do whatever you want to do when the button is tapped here
}
}
Hope this helps!
All patterns above are fine.
my two cents, in case You add by code (for example multiple different cells and so on..)
there is a FAR simple solution.
As buttons allow to specify a "target" You can pass directly the controller AND action to cell/button when setting it.
In controller:
let selector = #selector(self.myBtnAction)
setupCellWith(target: self, selector: selector)
...
in custom cell with button:
final func setupCellWith(target: Any? selector: Selector){
btn.addTarget(target,
action: selector,
for: .touchUpInside)
}
Add target for that button.
button.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
Set tag of that button since you are using it.
button.tag = indexPath.row
Achieve this by subclassing UITableViewCell. button on that cell, connect it via outlet.
To get the tag in the connected function:
#objc func connected(sender: UIButton){
let buttonTag = sender.tag
}
2 am answer: You're over thinking this. Create a custom TableViewCell class; set the prototype cell class to your new custom class; and then create an IBAction.

Table view cell elements not able to click and get data

I have one table view and inside that i placed one main view. And inside that main view i placed one button.And when ever use click on my cell button. I need to get the cell title label.This is what i need. But i tried following below code. Not sure what i am missing out. It not at all calling my cell.add target line.
Code in cell for row at index:
cell.cellBtn.tag = indexPath.row
cell.cellBtn.addTarget(self, action:#selector(self.buttonPressed(_:)), for:.touchUpInside)
#objc func buttonPressed(_ sender: AnyObject) {
print("cell tap")
let button = sender as? UIButton
let cell = button?.superview?.superview as? UITableViewCell
let indexPath = tableView.indexPath(for: cell!)
let currentCell = tableView.cellForRow(at: indexPath!)! as! KMTrainingTableViewCell
print(indexPath?.row)
print(currentCell.cellTitleLabel.text)
}
I even added a breakpoint, still it not at calling my cell.addTarget line
Tried with closure too. In cell for row at index:
cell.tapCallback = {
print(indexPath.row)
}
In my table view cell:
var tapCallback: (() -> Void)?
#IBAction func CellBtndidTap(_ sender: Any) {
print("Right button is tapped")
tapCallback?()
}
Here that print statement is getting print in console.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var list = [String]()
#IBOutlet weak var tableView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
cell.saveButton.tag = indexPath.row
//cell.saveButton.accessibilityIdentifier = "some unique identifier"
cell.tapCallback = { tag in
print(tag)
}
return cell
}
}
class MyTableViewCell: UITableViewCell {
// MARK: - IBOutlets
#IBOutlet weak var saveButton: UIButton!
// MARK: - IBActions
#IBAction func saveTapped(_ sender: UIButton) {
tapCallback?(sender.tag)
}
// MARK: - Actions
var tapCallback: ((Int) -> Void)?
}
Actually this is not a good programming practice to add the button (which contains in table view cell) target action in view controller. We should follow the protocol oriented approach for it. Please try to under stand the concept.
/*This is my cell Delegate*/
protocol InfoCellDelegate {
func showItem(item:String)
}
/*This is my cell class*/
class InfoCell: UITableViewCell {
//make weak reference to avoid the Retain Cycle
fileprivate weak var delegate: InfoCellDelegate?
//Outlet for views
#IBOutlet var showButton: UIButton?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
//This is the public binding function which will bind the data & delegate to cell
func bind(with: DataModel?, delegate: InfoCellDelegate?, indexPath: IndexPath) {
//Now the bind the cell with data here
//.....
//Assign the delegate
self.delegate = delegate
}
//Button action
#IBAction func rowSelected(sender: UIButton) {
self.delegate?.showItem(item: "This is coming from cell")
}
}
/*Now in your ViewController you need to just confirm the InfoCellDelegate & call the bind function*/
class ListViewController: UIViewController {
//Views initialisation & other initial process
}
//Table view Delegate & Data source
extension ListViewController: UITableViewDataSource, UITableViewDelegate {
/**
Configure the table views
*/
func configureTable() {
//for item table
self.listTable.register(UINib.init(nibName: "\(InfoCell.classForCoder())", bundle: nil), forCellReuseIdentifier: "\(InfoCell.classForCoder())")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InfoCell") as! InfoCell
cell.bind(with: DataModel, delegate: self, indexPath: indexPath)
return cell
}
}
extension ListViewController: InfoCellDelegate {
func showItem(item) {
print(item)
}
}

Access to some values from another class in Swift

I have a TableViewCell class like this:
class CampaignsTableViewCell: UITableViewCell {
#IBOutlet weak var activateButton: UIButton!
#IBOutlet weak var titleCampaignPlaceholder: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
setUpButton()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
private func setUpButton(){
activateButton.backgroundColor = .clear
activateButton.layer.cornerRadius = 5
activateButton.layer.borderWidth = 1
activateButton.layer.borderColor = UIColor.blue.cgColor
}
}
And, in another class which is a ViewController I have my UITableView methods:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let rowNumber = indexPath.row
let cellIdentifier = "CampaignTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? CampaignsTableViewCell else {
fatalError("The dequeued cell is not an instance of TableViewCellController.")
}
cell.titleCampaignPlaceholder.text = campaignsArray[rowNumber].campaignName
return cell
}
I need to use my activateButton in my UITableView method in order to access to campaignsArray. I have another method which requieres values from that array, so I need that method is called every time activateButton is pressed from my UITableView.
Any idea ?
Thank you very much
What I like doing in those cases where you have a button inside your UITableViewCell is the following:
Give the cell a closure that is called when tapping on the button like so
class CampaignsTableViewCell: UITableViewCell {
... all your code....
// give your cell a closure that is called when the button is pressed
var onButtonPressed: ((_ sender: UIButton) -> ())?
#IBAction func buttonPressed(sender: UIButton) { // wire that one up in IB
onButtonPressed?(sender)
}
}
and then inside your TableViewController
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CampaignsTableViewCell
cell.titleCampaignPlaceholder.text = campaignsArray[rowNumber].campaignName
cell.onButtonPressed = { [weak self] sender in
// Do your magic stuff here
}
return cell
Hope that helps
Your cell will get that event, not tableView. What you need to do is:
Create protocol inside your cell:
protocol CampaignsTableViewProtocol{
func actionButtonPressed(row: Int)
}
class CampaignsTableViewCell: UITableViewCell {
#IBOutlet weak var activateButton: UIButton!
#IBOutlet weak var titleCampaignPlaceholder: UILabel!
// keep info about row
var rowIndex: Int = -1
// create delegate that will let your tableView about action button in particular row
var delegate : CampaignsTableViewProtocol?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
setUpButton()
self. activateButton.addTarget(self, action: #selector(self.activatePressed), for: UIControlEvents.touchDown)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func activatePressed(){
self.delegate?.actionButtonPressed(row :rowIndex)
}
private func setUpButton(){
activateButton.backgroundColor = .clear
activateButton.layer.cornerRadius = 5
activateButton.layer.borderWidth = 1
activateButton.layer.borderColor = UIColor.blue.cgColor
}
}
Your tableViewController needs to adopt this protocol:
class MyTableViewController: UITableViewDelegate, UITableViewDataSource, CampaignsTableViewProtocol {
// rest of the code
}
Also, you will need to implement delegate function in your tableViewController:
func actionButtonPressed(row: Int) {
// get campaign you need
let campaign = campaignsArray[row]
// rest of the code
}

Get data from all UITableViewCells separately

I have a TableViewController that returns four cells. Each cell has three buttons in it. The cells themselves don't have any interaction or segues attached because I don't need the user to interact with the cells, just the three buttons in the cells.
I have an empty array called selections and if each button is pressed I want to append an item to that array. So far I can't find any method that will keep track of each button that's pressed.
What method can I put this code in?
if cell.yes.isSelected == true {
firstChoice = 1
selections.append(firstChoice)
print(selections.count, selections.reduce(0, +))
}
Such that it will work for ALL of the cells loaded by my TableViewController?
In order to achieve what you want you need to create delegates for both the custom cell and button.
//here is the view controller class
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CustomCellDelegate {
var firstChoice = 0
var selections = Array<Any>()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as? CustomCell
cell?.delegate = self
return cell!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func cellButtonTapped(_ cell: CustomCell) {
if cell.isSelected {
firstChoice = 1
selections.append(firstChoice)
}
}
}
The custom cell class
protocol CustomCellDelegate {
func cellButtonTapped(_ cell: CustomCell)
}
class CustomCell: UITableViewCell, CustomButtonDelegate {
var delegate: CustomCellDelegate?
#IBOutlet weak var button1: CustomButton!
#IBOutlet weak var button2: CustomButton!
#IBOutlet weak var button3: CustomButton!
override func awakeFromNib() {
super.awakeFromNib()
button1.delegate = self
button2.delegate = self
button3.delegate = self
}
func buttonTapped() {
self.isSelected = !self.isSelected
if let delegate = delegate {
delegate.cellButtonTapped(self)
}
}
}
And the custom button
protocol CustomButtonDelegate{
func buttonTapped()
}
class CustomButton: UIButton {
var delegate: CustomButtonDelegate?
override func awakeFromNib() {
super.awakeFromNib()
self.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
func buttonTapped(_ sender: AnyObject) {
if let delegate = delegate {
delegate.buttonTapped()
}
}
}
Both of them with their protocols
First of all - you should keep information about pressed button in your ViewController - not inside the table cells.
Table cells will be reused - and you will lose that information.
Best way to do that will be to use a custom delegate between cells and TableViewController. While creating each cell, you make:
cell.delegate = self
and inside a cell when the buttons are pressed, you call this delegate methods - let's say didPressButton1 didPressButton2.
Also if you want to have this state to be persistent (for example to disable, enable some button) while creating cells in your TableViewController you need to pull existing data and apply it to the cell itself - again TableViewCells are reused.
I don't know your specification, but it seems you could add a different target to each button:
button1?.addTarget(self, action:#selector(self.button1Clicked), forControlEvents: .TouchUpInside)
...
button3?.addTarget(self, action:#selector(self.button3Clicked), forControlEvents: .TouchUpInside)
and then you could have:
func button1Clicked() {
firstChoice = 1
selections.append(firstChoice)
}
...
func button3Clicked() {
firstChoice = 3
selections.append(firstChoice)
}
This way if button number 1 get clicked, button1Clicked() is fired and you can do your work as intended.

how to handle button click for each button in each row of UITableView

I have a UITableView (with a Custom class called CellModelAllNames for each row). Each Row has a Label and a button.
My question is: When btn_addRecording (i.e. the '+' button is clicked on any/each of the rows, how do I get the lbl_name.text, the label name shown, and show a pop up in the ViewController itself. I want to get additional information in the pop up and then save all the info (including the lbl_name to a database).
CellModelAllNames for each row layout:
import UIKit
class CellModelAllNames: UITableViewCell {
#IBOutlet weak var lbl_name: UILabel!
#IBOutlet weak var btn_addRecording: UIButton!
#IBAction func btnAction_addRecording(sender: AnyObject) {
println("clicked on button in UITableViewCell")
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func setCell(setBabyName: String) {
self.lbl_name.text = setBabyName
}
}
Here's the code of my ViewController:
import UIKit
class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tbl_allNames: UITableView!
var arrayOfNames: [Name] = [Name]()
override func viewDidLoad() {
super.viewDidLoad()
self.tbl_allNames.delegate = self
self.tbl_allNames.dataSource = self
self.tbl_allNames.scrollEnabled = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CellModelAllNames = self.tbl_allNames.dequeueReusableCellWithIdentifier("CellModelAllNames") as! CellModelAllNames
let name = arrayOfNames[indexPath.row]
cell.setCell(name.name)
println("in tableView, cellforRowatIndex, returning new cells")
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayOfNames.count
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
}
You can use standard UIKit methods to get the cell and its data:
func tappedButton(sender : UIButton) {
let point = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(point)!
let name = arrayOfNames[indexPath.row]
// do something with name
}
You can add button action in your ViewController
1) In your function cellForRowAtIndexPath assign button's tag as index (ie. indexPath.row)
cell.btn_addRecording.tag = indexPath.row
2) Add target and action for your button :
cell.btn_addRecording.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
3) Add action in ViewControler (ie. save info in database)
func buttonPressed(button: UIButton!)
{
// Add your code here
let name = arrayOfNames[button.tag]
}

Resources