I have this tableView:
but I can't recognize when user selects the UISwitch in the row.
I know I need an #IBAction in the UITableViewCell class but I didn't found a guide that works for me.
I'd like to print the indexPath of the row when user clicks on the UISwitch.
This is my class for cell:
class TicketsFilterCell: UITableViewCell {
#IBOutlet weak var textFilter: UILabel!
#IBOutlet weak var switchFilter: UISwitch!
class var reuseIdentifier: String? {
get {
return "TicketsFilterCell"
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
How can I do?
Thanks in advance.
Edit
My cellForRowAtIndexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: TicketsFilterCell? = tableView.dequeueReusableCellWithIdentifier("cellTicketFilter", forIndexPath: indexPath) as? TicketsFilterCell
cell?.delegate = self
if(indexPath.section == 0) {
let text = status[indexPath.row]?.capitalizedString
cell?.textFilter.text = text
} else if(indexPath.section == 1) {
let text = queues [indexPath.row]?.capitalizedString
cell?.textFilter.text = text
} else if(indexPath.section == 2) {
let text = types[indexPath.row]?.capitalizedString
cell?.textFilter.text = text
} else if(indexPath.section == 3) {
let text = severities[indexPath.row]?.capitalizedString
cell?.textFilter.text = text
}
return cell!
}
Jigen,
Here is what you can do :)
Declare a protocol in your cell class lets call it as CellProtocol.
protocol CellProtocol : class {
func switchButtonTapped(WithStatus status : Bool, ForCell myCell : TicketsFilterCell)
}
Declare a variable in your TicketsFilterCell class to hold delegate,
weak var delegate : CellProtocol!
When user taps on your switch trigger the delegate as
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.delegate .switchButtonTapped(WithStatus: selected, ForCell: self)
}
In your TableViewController confirm to protocol using
class ViewController: UITableViewController,CellProtocol {
Now in your ViewController's cellForRoAtIndexPath, for each cell set the delegate as self.
cell.delegate = self
Finally implement the delegate method as follow
func switchButtonTapped(WithStatus status: Bool, ForCell cell: TicketsFilterCell) {
let indexPath = self.tableView .indexPathForCell(cell)
print("cell at indexpath \(indexPath) tapped with switch status \(status)")
}
EDIT :
For each cell you have dragged the IBOutlet for switch :) Now all you need is IBAction from UISwitch to the cell :)
#IBAction func switchTapped(sender: UISwitch) {
self.delegate.switchButtonTapped(WithStatus: sender.on, ForCell: self)
}
Thats it :)
Use this
cell.yourSwitch.tag=indexPath.row;
in cellForRowAtIndexPath method
Based on that tag perform your code in switch on/off action.
Add an #IBAction from the switch to the view controller itself. In the IBAction function convert the position of the sender(UISwitch) to tableView's frame. Then, find the cell at that point in tableView.
I believe this is the most elegant way to add actions to table view cells.
The biggest problem with the tag property is that its use always starts out simple, but over time the logic (or lack thereof) evolves into a fat unreadable mess.
This is easier than creating a protocol and implementing the delegate
Below is the IBAction connected to the switch's valueChanged action
#IBAction func switchValueChanged(sender: AnyObject) {
let switchButton = sender as! UISwitch
let buttonPosition = switchButton.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)!
NSLog("switch at index %d changed value to %#", indexPath.row, switchButton.on)
}
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 currently have a table with a custom cell that contains a button and a label. Everything is working fine, the label displays the product name and the button responds to a button tap.
What I would like to be able to do is animate the width of the row/cell where the button was tapped.
Here is the code I currently have...
Main ViewController:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = items[indexPath.row]
cell.displayProductName.text = data.productName
cell.buttonAction = { sender in
print("Button tapped...")
}
return cell
}
}
Custom Cell
class CustomCell: UITableViewCell {
var buttonAction: ((UIButton) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func activeInactive(_ sender: UIButton) {
self.buttonAction?(sender)
}
}
Table View
How can I animate the width of the row where the button was tapped?
You could subclass UIButton and add an index property where you can store the represented index for the button:
import UIKit
class UIButtonWithIndex: UIButton {
var representedIndex: Int?
}
Then you should use this class in the storyboard instead UIButton.
After that add the button as an outlet in your cell and connect in in Interface Builder.
class CustomCell: UITableViewCell {
#IBOutlet weak var button: UIButtonWithIndex!
var buttonAction: ((UIButtonWithIndex) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func activeInactive(_ sender: UIButtonWithIndex) {
self.buttonAction?(sender)
}
// Reset the represented index when reusing the cell
override func prepareForReuse() {
//Reset content view width
button.representedIndex = nil
}
}
Then when you dequeue the cell in cellForRowAt indexPath set the representedIndex property. Now in your buttonAction you should have the index of the row in which the button was tapped.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = items[indexPath.row]
cell.displayProductName.text = data.productName
cell.button.representedIndex = indexPath.item
cell.buttonAction = { sender in
print("Button tapped... \(sender.representedIndex)")
}
return cell
}
Once you have the index you can retrieve the cell with cellForRowAtIndexPath
I'd recommend to:
Create subview, let's call it "resizeableContentView"
Add "resizeableContentView" as a child to cell's "contentView"
Add your views to "resizeableContentView" as a child
Set .clearColor for cell and "contentView" background color if needed
Set width for "resizeableContentView" by action
Don't forget reset cell when it's reused
you can try this boilerplate code:
cell.buttonAction = { sender in
UIView.animate(withDuration: 0.5) {
cell.frame = CGRect(x: 0, y: 0, width: 150, height: 50)
}
}
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 have a CollectionViewController with 3 buttons populated. The view works well, but how do I select different view controllers based on the button selected? I added the button as an action, but I don't know how to specify which button is selected so I can send the user to different viewcontrollers.
import UIKit
private let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController {
var imageArray = [UIImage(named: "tempOwl.png"), UIImage(named: "tempPuzzle.png"), UIImage(named: "tempHouse.png")]
override func viewDidLoad() {
super.viewDidLoad()
self.clearsSelectionOnViewWillAppear = false
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
#IBAction func menuButton(sender: UIButton) {
let controller = storyboard?.instantiateViewControllerWithIdentifier("myHome")
presentViewController(controller!, animated: true, completion: nil)
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as UICollectionViewCell
let imageView = cell.viewWithTag(1) as! UIButton
imageView.setBackgroundImage(self.imageArray[indexPath.row], forState: .Normal)
return cell
}
}
The button action method for your button in cell
func buttonAction(sender : UIButton) {
var selectedButtonCell = sender.superview as! UICollectionViewCell
//Incase your button is inside cell.contentview
// var selectedButtonCell = sender.superview.superview as! UICollectionviewCell
var indexPath = collectionView.indexPathForCell(selectedButtonCell)
if indexPath.row == 0 {
//Button in first cell is selected
//Send user to first button view controller
}
}
I suggest to create a class of type UICollectionViewCell and create the IBAction in the cell.
And set the collectionViewController as delegate. So you can pass values.
So you can create a function in the collection view controller and give it a param like a int 1 for cell 1.
If you have multiple buttons in the cell you should implement a delegate. Then, set your view controller as a delegate of the cell. Sample implementation of such a delegate could look like:
protocol YourCustomCellDelegate {
func firstButtonPressed(cell: YourCustomCell)
func secondButtonPressed(cell: YourCustomCell)
}
class YourCustomCell : UICollectionViewCell {
var delegate:YourCustomCellDelegate?
#IBOutlet weak var firstButton: UIButton!
#IBOutlet weak var secondButton: UIButton!
#IBAction func firstButtonTapped(sender: AnyObject) {
delegate?.firstButtonPressed(self)
}
#IBAction func secondButtonTapped(sender: AnyObject) {
delegate?.secondButtonPressed(self)
}
}
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.