I'm trying to change the color of a button when pressed. Currently its inside a table view cell.
The way I'm doing it is adding as so:
#IBAction func upVote(sender: AnyObject) {
sender.setImage(UIImage(named: "bUpVote"), forState: .Normal)
}
and this is done inside the cell class (not the view controller class).
It works, but the change also applies to every third cell that follows it for the rest of the table.
Any work around? Thanks!
There are many way to solve this issue, one of the method is as follows
Add this to your customCell class,
#objc protocol MyTableViewCellDelegate {
func controller(controller: MyTableViewCell, button: UIButton, selectedButtonIndexPath : NSIndexPath)
}
class MyTableViewCell: UITableViewCell {
var delegate: AnyObject?
var indexPath : NSIndexPath?
#IBOutlet weak var button: UIButton!//outlet of button
button Action
#IBAction func buttonAction(sender: UIButton)//IF the sender type is AnyObject, you have to change it as UIButton
{
self.delegate?.controller(self, button: sender, selectedButtonIndexPath: indexPath!)
}
Add this to your ViewController class that has UITableView
class MyTableViewController: UITableViewController, MyTableViewCellDelegate { // I created a subClass of UITableViewController, your's may be different
var arraySelectedButtonIndex : NSMutableArray = []//global declaration
Since i created my custom cell using xib, in viewDidLoad()
tableView.registerNib(UINib(nibName: "MyTableViewCell", bundle: nil), forCellReuseIdentifier: "CustomCell")//Since, I use custom cell in xib
define delegate of custom cell by adding this
func controller(controller: MyTableViewCell, button: UIButton, selectedButtonIndexPath : NSIndexPath)
{
if(arraySelectedButtonIndex .containsObject(selectedButtonIndexPath)==false)
{
arraySelectedButtonIndex.addObject(selectedButtonIndexPath)
button.setImage(UIImage(named: "bUpVote") , forState: .Normal)
}
else
{
arraySelectedButtonIndex.removeObject(selectedButtonIndexPath)//If you need to set Deselect image
button.setImage(UIImage(named: "deselectImage") , forState: .Normal)//If you need to set Deselect image
}
}
In tableView dataSource (cellForRowAtIndexPath)
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! MyTableViewCell
cell.delegate = self
cell.indexPath = indexPath
if(arraySelectedButtonIndex .containsObject(indexPath))
{
cell.button.setImage(UIImage(named: "bUpVote"), forState: .Normal)
}
else
{
cell.button.setImage(UIImage(named: "deselectImage"), forState: .Normal)//If you need to set Deselect image
}
return cell
}
It's because cells are reused by the tableView. if you need to persist the state of subviews in the cell, you need to update your data source and reflect the changes in cellForRowAtIndexPath method.
This is not the way to do it. You store the state of the button in your model. Eg: say store the item's upvoted status in your model :
class Post
{
var title : String
var upvoted : Bool
}
How to get the index path ?
Move the IBAction method on your custom tableview subclass. Add a property called delegate to the cell and set it to your controller in cellForRowAtIndexPath: . Now in the action method inform the delegate.
I have described this in detail here : https://stackoverflow.com/a/32250043/1616513
Now when the user upvotes you update the model :
#IBAction func upVotedInCell(sender: UITableViewCell) {
var indexPath = self.tableView.indexPathForCell(sender)
self.items[indexPath].upvoted = true
self.tableView.reloadRowsAtIndexPaths([indexPath],UITableViewRowAnimation.None)
}
Related
I have a custom cell that has a xib, this cell contains a button, when the button is pressed I want to do an action , but not inside my custom cell class but from inside the viewcontroller that contains the tableview of the custom cell, any help please?
First of all you should write a protocol for example:
protocol CustomCellDelegate {
func doAnyAction(cell:CustomUITableViewCell)
}
Then inside your custom cell class declare:
weak var delegate:CustomCellDelegate?
and inside your IBAction in the custom cell class:
#IBAction func onButtonTapped(_ sender: UIButton) {
delegate?.doAnyAction(cell: self)
//here we say that the responsible class for this action is the one that implements this delegate and we pass the custom cell to it.
}
Now in your viewController:
1- Make your view controller implement CustomCellDelegate.
2- In your cellForRow when declaring the cell don't forget to write:
cell.delegate = self
3- Finally call the function in your viewcontroller:
func doAnyAction(cell: CustomUITableViewCell) {
let row = cell.indexPath(for: cell)?.row
//do whatever you want
}
}
You can use delegate pattern. Create a custom protocol.
protocol CustomTableViewCellDelegate {
func buttonTapped() }
and on tableview cell conform delegate
class CustomTableViewCell: UITableViewCell {
var delegate: CustomTableViewCellDelegate!
#IBAction func buttonTapped(_ sender: UIButton) {
delegate.buttonTapped()
} }
table view data source
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath) as! CustomTableViewCell
cell.delegate = self
return cell
}
conform protocol(delegate) from table view controller or view controller
extension TestViewController: CustomTableViewCellDelegate {
func buttonTapped() {
print("do something...")
} }
Just get the UIButton in your cellForRowAtIndexpath.
Then write the following code.
button.addTarget(self, action:#selector(buttonAction(_:)), for: .touchUpInside).
func buttonAction(sender: UIButton){
//...
}
Add action for button from tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) method from your viewController.
cell.btn.tag = indexPath.row;
cell.btn.addTarget(self, action:#selector(handleButtonClicked(_:)), for: .touchUpInside).
Write selector function in your view Controller:
func handleButtonClicked(sender: UIButton){
int cellofClickedbutton = sender.tag;
//...
}
In your cell define a protocol:
protocol MyCellDelegate: class {
func buttonTapped()
}
and define a delegate variable:
weak var delegate: MyCellDelegate?
Make your viewController conform to the defined protocol.
When creating the cell in the viewController, assign it the delegate:
cell.delegate = self
When the button in the cell is tapped, send the delegate appropriate message:
delegate?.buttonTapped()
In your viewController implement the method:
func buttonTapped() {
//do whatever you need
}
You can pass as an argument the cell's index, so the viewController knows which cell button was tapped.
I have ViewController with TableView. In each cell of this TableView there are few buttons (so I can't use didSelectRow).
I need to get parent ViewController from action when buttons are pressed. So I add this function to my custom cell class:
#IBAction func editBtnPressed (_ sender: Any) {
}
I need to get to self in order to add some subviews to it.
How can I access root ViewController as self?
I guess you should do it by creating property of your controller in cell class and assign property value when after creating cell in cellForRowAtIndexPath method.
For example:
in cell class
weak var yourController : YourViewController?
in cellForRowAtIndexPath
cell.yourController = self
then you can access the YourViewController in editBtnPressed action.
But i suggest you to do by creating button action programmatically in your controller class. that's the good approach.
for example:
class YourCellClass: UITableViewCell {
#IBOulet var myBtn: UIbutton!
...
}
Yourcontroller class
in cellForRowAtIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! YourCellClass
cell.myButton.addTarget(self, action: #selector(self. editBtnPressed), for: .touchUpInside)
and in editBtnPressed
func editBtnPressed (_ sender: Any) {
// you can access controller here by using self
}
This question already has answers here:
Correct way to setting a tag to all cells in TableView
(3 answers)
Closed 6 years ago.
I have a custom UITableViewCell in which I have connected my UIButton using Interface Builder
#IBOutlet var myButton: UIButton!
Under cell configuration of UITableViewController, I have the following code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var customCell = tableView.dequeueReusableCell(withIdentifier: self.MY_CELL_IDENTIFIER, for: indexPath) as! myCustomCell
// CONFIGURE OTHER CELL PARAMETERS
customCell.myButton.tag = indexPath.row
customCell.myButton.addTarget(self, action: #selector(myButtonPressed), for: UIControlEvents.touchUpInside)
return customCell
}
Finally, I have
private func myButtonPressed(sender: UIButton!) {
let row = sender.tag
print("Button Sender row: \(row)")
}
This code is not working, unless I change the function definition to below:
#objc private func myButtonPressed(sender: UIButton!) {
let row = sender.tag
print("Button Sender row: \(row)")
}
Is there a better way to implement UIButton on custom UITableViewCell in Swift 3
I am not a big fan using view tags. Instead of this, you could use the delegate pattern for your viewController to be notified when a button has been hit.
protocol CustomCellDelegate: class {
func customCell(_ cell: UITableViewCell, didPressButton: UIButton)
}
class CustomCell: UITableViewCell {
// Create a delegate instance
weak var delegate: CustomCellDelegate?
#IBAction func handleButtonPress(sender: UIButton) {
self.delegate?.customCell(self, didPressButton: sender)
}
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var customCell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) as! CustomCell
// CONFIGURE OTHER CELL PARAMETERS
//Assign the cell's delegate to yourself
customCell.delegate = self
return customCell
}
}
extension ViewController: CustomCellDelegate {
// You get notified by the cell instance and the button when it was pressed
func customCell(_ cell: UITableViewCell, didPressButton: UIButton) {
// Get the indexPath
let indexPath = tableView.indexPath(for: cell)
}
}
Yes, there is a smarter and better way to do this. The main problem of your method is that it only work if no insert, delete or move cells operation occurs. Because anyone of these operations can change de indexPath of the cells that were created for a different indexPath.
The system I use is this:
1.- Create a IBAction in your cell class MyCustomCell (With uppercase M. It is a class, so name it properly).
2.- Connect the button to that action.
3.- Declare a protocol MyCustomCellDelegate with, at least, a method
func myCustomCellButtonAction(_ cell:MyCustomCell)
and add a property to MyCustomCell
var delegate : MyCustomCellDelegate?
4.- Set the view controller as MyCustomCellDelegate
In the method of MyCustomCell connected to the button invoke the delegate method:
delegate?.myCustomCellButtonAction( self )
5.- In the view controller, in the method cellForRowAt:
customCell.delegate = self
6.- In the view controller, in the method myCustomCellButtonAction:
func myCustomCellButtonAction( _ cell: MyCustomCell ) {
let indexPath = self.tableView.indexPathForCell( cell )
// ...... continue .....
}
You can also use delegates to do the same.
Directly map the IBAction of button in your custom UITableViewCell Class.
Implement the delegate methods in viewcontroller and On button action call the delegate method from custom cell.
I have a UISwitch in a tableviewcontroller, and when the switch is toggled I want it to change the value of a boolean variable in an array I created inside the view controller, that the cell is related to. Kind of like the Stock Alarm App on IOS, where each cell has a UISwitch, and toggling the switch will turn off each individual alarm. So with the UISwitch, with its selector code, this is inside the cellForRowAtIndexPath method
//switch
let lightSwitch = UISwitch(frame: CGRectZero) as UISwitch
lightSwitch.on = false
lightSwitch.addTarget(self, action: #selector(switchTriggered), forControlEvents: .ValueChanged)
//lightSwitch.addTarget(self, action: "switchTriggered", forControlEvents: .ValueChanged )
cell.accessoryView = lightSwitch
I want it to do this
func switchTriggered(a: Int) {
changeValueOfArray = array[indexPath.row]
}
I don't have the code written for that part yet, but my question is, How can i let the switchTriggered function see the indexPath.row value, without passing it as an argument to the function because I can't because its a selector?
let lightSwitch = UISwitch(frame: CGRectZero) as UISwitch
lightSwitch.on = false
lightSwitch.addTarget(self, action: #selector(switchTriggered), forControlEvents: .ValueChanged)
lightSwitch.tag = indexpath.row
cell.accessoryView = lightSwitch
Let save your boolean value in Array
func switchTriggered(sender: UISwitch) {
sender.on ? array[sender.tag]=1 : array[sender.tag]=0
}
}
The basic idea is that you can capture the cell for which the switch was flipped and then use tableView.indexPath(for:) to translate that UITableViewCell reference into a NSIndexPath, and you can use its row to identify which row in your model structure needs to be updated.
The constituent elements of this consist of:
Create a model object that captures the information to be shown in the table view. For example, let's imagine that every cell contains a name of a Room and a boolean reflecting whether the light is on:
struct Room {
var name: String
var lightsOn: Bool
}
Then the table view controller would have an array of those:
var rooms: [Room]!
I'd define a UITableViewCell subclass with outlets for the label and the switch. I'd also hook up the "value changed" for the light switch to a method in that cell. I'd also set up a protocol for the cell to inform its table view controller that the light switch was flipped:
protocol RoomLightDelegate: class {
func didFlipSwitch(for cell: UITableViewCell, value: Bool)
}
class RoomCell: UITableViewCell {
weak var delegate: RoomLightDelegate?
#IBOutlet weak var roomNameLabel: UILabel!
#IBOutlet weak var lightSwitch: UISwitch!
#IBAction func didChangeValue(_ sender: UISwitch) {
delegate?.didFlipSwitch(for: self, value: sender.isOn)
}
}
I'd obviously set the base class for the cell prototype to be this UITableViewCell subclass and hook up the #IBOutlet references as well as the #IBAction for the changing of the value for the switch.
I'd then have the UITableViewDataSource methods populate the cell on the basis of the Room properties:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rooms.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SwitchCell", for: indexPath) as! RoomCell
let room = rooms[indexPath.row]
cell.roomNameLabel.text = room.name
cell.lightSwitch.setOn(room.lightsOn, animated: false)
cell.delegate = self
return cell
}
Note, the above cellForRowAtIndexPath also specifies itself as the delegate for the cell, so we'd want to implement the RoomLightDelegate protocol to update our model when the light switch is flipped:
extension ViewController: RoomLightDelegate {
func didFlipSwitch(for cell: UITableViewCell, value: Bool) {
if let indexPath = tableView.indexPath(for: cell) {
rooms[indexPath.row].lightsOn = value
}
}
}
Now, I don't want you to worry about the details of the above. Instead, try to capture some of the basic ideas:
Bottom line, to your immediate question, once you know which cell was was updated, you can inquire with the UITableView to determine what NSIndexPath that UITableViewCell reference corresponds to, using tableView.indexPath(for:).
Swift 3 Update:
let lightSwitch = UISwitch(frame: CGRect.zero) as UISwitch
lightSwitch.isOn = false
lightSwitch.addTarget(self, action: #selector(switchTriggered), for: .valueChanged)
lightSwitch.tag = indexPath.row
cell?.accessoryView = lightSwitch
I have a custom UITableViewCell subclass and its associated xib. I have a UILabel and a UIButton in this cell and I have wired the touch up inside action of the button to the subclass.
What I need is when that button in the cell is tapped, to get the indexpath of the cell which has that button. And maybe send it back to the view controller via a delegate or something.
Since I'm inside a UITableViewCell subclass, I can't use a solution like this because I don't have a reference to the tableview from inside the cell subclass. Upon further investigation I found another solution and I implemented it in Swift like this.
import UIKit
class ContactCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
selectionStyle = .None
}
#IBAction func callButtonPressed(sender: UIButton) {
let indexPath = (self.superview as UITableView).indexPathForCell(self)
println("indexPath?.row")
}
}
But when I tap on the button, it crashes with an error message saying Swift dynamic cast failed.
Any idea what's wrong with my code?
Or I'm open to any other suggestions which would allow me to achieve the desired result in any other way.
Thank you.
Sounds like you need a delegate:
Delegates in swift?
Then just pass the cell itself as a parameter to the delegate, and then you can easily do tableView.indexPathForCell(cellFromDelegateMethod)
Hey you can use "Tag" of the button also.Inside the cellForRowAt method of table delegate u can tag the button with Indexpath.row . here is the example what i m tried to say.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// get ur cell nib .As it has a button
cell.startOrConntinuBtn.addTarget(self, action: #selector(sumbitOrContinue), for: .touchUpInside)
cell.startOrConntinuBtn.tag = indexPath.row }
and in the touch method "sumbitOrContinue" -
func sumbitOrContinue(sender: UIButton!) {
let tag = sender.tag
// do what you want to do like this example
let detail = self.detailList[tag]
let vc = self.storyboard?.instantiateViewController(withIdentifier: "mockExamInt") as! MockWindowVc
vc.detailId = detail.id
self.navigationController?.pushViewController(vc, animated: true)}
UIButton.Type really does not have member superview, but sender have
var cell: UITableViewCell = sender.superview.superview as UITableViewCell