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.
Related
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.
I want to enable the button in the viewcontroller when I fill in the textfield in the tableviewcell.
I don't know how to solve the problem.
Sorry. I am immature in English.
button(viewController) isEnable = false
fill textfields(inside tableviewcell)
button(viewController) isEnable = true
1. ViewController
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
#IBOutlet var tableView: UITableView!
#IBOutlet var viewLabel: UILabel!
#IBOutlet var clickBtn: UIButton!
var cell: CustomTableViewCell!
override func viewDidLoad() {
super.viewDidLoad()
self.clickBtn.isEnabled = false
cell.textFieldCell.addTarget(self, action: #selector(editingChanged), for: .editingChanged)
cell.textFieldCell2.addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
#objc func editingChanged(sender: UITextField) {
sender.text = sender.text?.trimmingCharacters(in: .whitespaces)
guard
let text1 = cell.textFieldCell.text, !text1.isEmpty,
let text2 = cell.textFieldCell2.text, !text2.isEmpty
else
{
self.clickBtn.isEnabled = false
return
}
clickBtn.isEnabled = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! CustomTableViewCell
return cell
}
}
2. CustomTableView
import UIKit
class CustomTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet var myLabel: UILabel!
#IBOutlet var textFieldCell: UITextField!
#IBOutlet var textFieldCell2: UITextField!
}
As the table view contains only one cell why do you use a table view at all?
And if you really need to use a table view why don't you use a static cell?
A simple solution is to move the target/action code from viewDidLoad into the didSet property observer of the cell
var cell: CustomTableViewCell! {
didSet {
cell.textFieldCell.addTarget(self, action: #selector(editingChanged), for: .editingChanged)
cell.textFieldCell2.addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.clickBtn.isEnabled = false
}
One option is to use delegate.
protocol CustomTableViewCellDelegate {
func editingChanged(_ String: yourString)
}
In table view cell
class TableViewCell: UITaleViewCell {
// declare the delegate
var delegate: CustomTableViewCellDelegate?
// and pass it like this from where you want to pass
delegate?.editingChanged(cookie)
}
For using in tableViewController. first assign the delegate to self in cell for row at indexpath method. Then implement the delegate in it.
extension CustomTableViewController: CustomTableViewCellDelegate {
func editingChanged(String: yourString) {
// use it accordingly
}
}
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
}
I created a table view and the tableViewCell is not clickable with one finger, but when I try to click the tableViewCell with two fingers the click event takes place. I don't know why this occurres. I created a custom cell in tableView.
InviteVC
import UIKit
class InvitePeopleVC: UIViewController, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource {
var nameArray = ["Alwin Lazar", "Ajith Ramesh CR", "Ebrahim KK", "Vishnu Prakash"]
var emailArray = ["alwin#xeoscript.com", "ajith#xeoscript.com", "ebrahim#xeoscript.com", "vishnu#xeoscript.com"]
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var doneImg: UIImageView!
#IBOutlet weak var nameTextFld: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
delegates()
uiModifications()
gestureRecognizers()
}
func delegates() {
tableView.dataSource = self
tableView.delegate = self
nameTextFld.delegate = self
}
func uiModifications() {
nameTextFld.attributedPlaceholder = NSAttributedString(string: "Name or email address", attributes: [NSForegroundColorAttributeName: UIColor.white])
}
func gestureRecognizers() {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(InvitePeopleVC.dismissKeyboard)))
self.doneImg.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(InvitePeopleVC.doneImgPressed)))
}
func dismissKeyboard() {
nameTextFld.resignFirstResponder()
}
func doneImgPressed() {
print("done Image tapped")
}
func inviteBtnPressed() {
print("invite button pressed")
}
// UITextFieldDelegate method
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == self.nameTextFld {
self.nameTextFld.resignFirstResponder()
}
return true
}
// TableView DataSource methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InviteCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! InviteCell
cell.nameLbl.text = nameArray[indexPath.row]
cell.emailLbl.text = emailArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
// TableView Delegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("selected row is \(indexPath.row)")
}
#IBAction func backBtnPressed(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
#InviteCell
import UIKit
class InviteCell: UITableViewCell {
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var emailLbl: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
}
UIViewController Images
TableView Attributes Inspector
InviteCell Atribute Inspector
In the code above, I'm trying to select a cell with one finger, but the selection does not happen.
Thanks in advance...
A more elegant way of dealing with the tap issue is:
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(AppController.dismissKeyboard))
view.addGestureRecognizer(tap)
//this is the KEY of the fix
tap.cancelsTouchesInView = false
This way you can keep your gesture recognizer and still get the table view action in one single tap/selection.
You have the following line in your set up code:
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(InvitePeopleVC.dismissKeyboard)))
That sets up a gesture recognizer for your whole view and that would swallow any touches on the main view. If you remove that, you should get the table cell selection working correctly :)
The Tap gesture you have added in the code is causing the issue. Tapgesture recogniser is listening to the user tap actions in the view. The cell select listner is being blocked by the added Tap gesture.
As #Fahim said, if you remove the tap gesture from your code, then cell selection will work smoothly.
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.