I have this:
let mapsbut = cell.viewWithTag(912) as! UIButton
mapsbut.addTarget(self, action: "mapsHit:", forControlEvents: UIControlEvents.TouchUpInside)
and
func mapsHit(){
// get indexPath.row from cell
// do something with it
}
How is this accomplished?
You can always use the tag from your button to pass or hold a value, or a var inside of your custom cell implementation. For example, if you have your button as an outlet in your UITableViewCell (for instance):
class MenuViewCell: UITableViewCell {
#IBOutlet weak var titlelabel: UILabel!
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: "mapsHit:", forControlEvents: UIControlEvents.TouchUpInside)
}
}
func mapsHit(sender: UIButton){
let indexPathOfThisCell = sender.tag
println("This button is at \(indexPathOfThisCell) row")
// get indexPath.row from cell
// do something with it
}
}
Notice here, that you need to set sender:UIButton as a parameter when you set "mapsHit:". This will be the button itself in wich the user has tapped.
Now, for this to work, your tag can not be "912". Instead when you build your cell, assign to the tag property of your button, the value of it's indexPath.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MenuViewCell", forIndexPath: indexPath) as! MenuViewCell
cell.titlelabel?.text = data[indexPath.row].description
cell.button.tag = indexPath.row
return cell
}
...one solution could be to have a var inside your class holding the indexPath of the last cell tapped, then you can use that value inside your mapsHit() function.
Related
I have a button and a label in a table view (I am using 8 rows )and for some reason when I click the first button I get indexPath nil error, but when I click the second button (2nd row) I get the first row label. When I click the 3rd row button, I get the second row label etc. Why are they misaligned. I want when I click the first row button to get the first row label etc. Please see the code below. Thank you !!
#objc func btnAction(_ sender: AnyObject) {
var position: CGPoint = sender.convert(.zero, to: self.table)
print (position)
let indexPath = self.table.indexPathForRow(at: position)
print (indexPath?.row)
let cell: UITableViewCell = table.cellForRow(at: indexPath!)! as
UITableViewCell
print (indexPath?.row)
print (currentAnimalArray[(indexPath?.row)!].name)
GlobalVariable.addedExercises.append(currentAnimalArray[(indexPath?.row)!].name)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? TableCell else {return UITableViewCell() }
// print(indexPath)
cell.nameLbl.text=currentAnimalArray[indexPath.row].name
// print("\(#function) --- section = \(indexPath.section), row = \(indexPath.row)")
// print (currentAnimalArray[indexPath.row].name)
cell.b.tag = indexPath.row
// print (indexPath.row)
cell.b.addTarget(self, action: #selector(SecondVC.btnAction(_:)), for: .touchUpInside)
return cell
}
Frame math is a worst-case scenario if you have no choice. Here you have a lot of choices.
For example why don't you use the tag you assigned to the button?
#objc func btnAction(_ sender: UIButton) {
GlobalVariable.addedExercises.append(currentAnimalArray[sender.tag].name)
}
A swiftier and more efficient solution is a callback closure:
In TableCell add the button action and a callback property. The outlet is not needed. Disconnect the outlet and connect the button to the action in Interface Builder. When the button is tapped the callback is called.
class TableCell: UITableViewCell {
// #IBOutlet var b : UIButton!
#IBOutlet var nameLbl : UILabel!
var callback : (()->())?
#IBAction func btnAction(_ sender: UIButton) {
callback?()
}
}
Remove the button action in the controller.
In cellForRow assign a closure to the callback property
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// no guard, the code must not crash. If it does you made a design mistake
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableCell
let animal = currentAnimalArray[indexPath.row]
cell.nameLbl.text = animal.name
cell.callback = {
GlobalVariable.addedExercises.append(animal.name)
}
return cell
}
You see the index path is actually not needed at all. The animal object is captured in the closure.
You already pass indexPath.row with button tag. Use the tag as index simply
#objc func btnAction(_ sender: UIButton) {
GlobalVariable.addedExercises.append(currentAnimalArray[sender.tag].name)
}
I am trying to implement play/pause button in tableview cell. each cell having single button, whenever user click it, It should change button image also need to call required function, also after scroll it should same.
Below code I am using
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) - > UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("productCell") as ? SepetCell
cell.onButtonTapped = {
//Do whatever you want to do when the button is tapped here
}
See first of all every button of the tableView Cell will have a unique tag associated with it, so in order to update the button of a particular cell, you will have to define the tag of a button in the cells and then pass this tag to your function to perform action on that particular button of the selected cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_identifier", for:
indexPath) as! CellClass
cell.button.tag = indexPath.row
cell.button.addTarget(self, action: #selector(playpause), for: .touchUpInside)
}
#objc func playpause(btn : UIButton){
if btn.currentImage == UIImage(named: "Play") {
btn.setImage(UIImage(named : "Pause"), forState: .Normal)
}
else {
btn.setImage(UIImage(named : "Play"), forState: .Normal)
}
// perform your desired action of the button over here
}
State of the art in Swift are callback closures. They are easy to implement and very efficient.
In the data source model add a property
var isPlaying = false
In Interface Builder select the button in the custom cell and press ⌥⌘4 to go to the Attributes Inspector. In the popup menu State Config select Default and choose the appropriate image from Image popup, Do the same for the Selected state.
In the custom cell add a callback property and an outlet and action for the button (connect both to the button). The image is set via the isSelected property.
#IBOutlet weak var button : UIButton!
var callback : (()->())?
#IBAction func push(_ sender: UIButton) {
callback?()
}
In the controller in cellForRow add the callback, item is the current item of the data source array. The state of the button is kept in isPlaying
cell.button.isSelected = item.isPlaying
cell.callback = {
item.isPlaying = !item.isPlaying
cell.button.isSelected = item.isPlaying
}
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'm trying to build a custom cell with different button inside, but when I tap "follow button" in on cell and color this button it seems that other follow button get a color too...Also if I scroll up and down my tableView other button randomly get color...I'm still learning how to use custom cell...
Here there is my custom cell
class customCell: UITableViewCell
{
#IBOutlet weak var follow: UIButton!
#IBOutlet var comment: UIButton?
#IBOutlet var share: UIButton?
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cellPost = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! customCell
cellPost.tag = Array(postToPrint.keys)[indexPath.row]
cellPost.follow?.tag = Array(postToPrint.keys)[indexPath.row]
cellPost.follow?.addTarget(self, action: "follow:", forControlEvents: .TouchUpInside)
return cellaPost
}
}
Here there is my function follow
func follow(sender: UIButton)
{
let postSelected = sender.tag
// In order to get indexPath from int values
let indexPath = NSIndexPath(forRow: postSelected, inSection: 1)
let cell = table.cellForRowAtIndexPath(indexPath) as! customCell
cell.follow.setTitleColor(UIColor.redColor(), forState: UIControlState)
idPostClicked = postSelected
sendHttpRequest(postClicked : idPostClicked)
}
I'm giving as tag to every cell my dictionary keys. Later I want to take cell.tag and send it as parameter in sendHttpRequest method. If I put my button function inside my customCell I want to get the cell in which there is my followButton so I can send a request with cell.tag.
However text get colored in other cell when I click only one...:/
Hmm, I feel like you are going about this in the wrong way. I would rather move the func follow into the cell itself. There's no reason for the tableView to handle this logic since it's the cell who'd like to change elements it can access on its own. Also, if you move the logic into the cell then you won't need tags or add targets etc.
class customCell: UITableViewCell
{
#IBOutlet weak var follow: UIButton!
#IBOutlet var comment: UIButton?
#IBOutlet var share: UIButton?
// Connect the follow button to this function as an action
#IBAction followPressed(sender: UIButton) {
follow.setTitleColor(UIColor.redColor(), forState: UIControlState.Normal)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellPost = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! customCell
return cellaPost
}
This should do the trick, I believe.
Edit
Added some missing code for UIControlState for the button. If this doesn't work could you upload a sample project? Hard to pinpoint what you are trying to do with only this piece of code.
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)
}