Detect UIButton while using custom UITableViewCell - uitableview

I want to implement a custom UITableViewCell class that is made up of subviews such as, labels, buttons, and images. These cells will display content fetched from the web using an API.
I do not want to implement the UITableView delegate method didSelectRowAtIndexPath as this will make the entire cell selectable. Only the button in the cell should be able to trigger any action.
The button is connected from the storyboard to the custom UITableViewCell via IBOutlet. The addTarget(_:action:forControlEvents:) method is called on the UIButton in cellForRowAtIndexPath of the UITableViewController class.
The roadblock occurs when we want to detect the indexPath of the selected cell in the Selector function of the button.
This is how the indexPath for the selected cell can be detected in the Selector function
#IBAction func doSomething(sender: AnyObject) {
var location: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
var indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(location)!
println("The indexPath for Selected Cell is - \(indexPath.row)")
}
Although, this successfully gets me around the issue, my question is;
1) Have you found an alternate way of being able to use UIButtons to pass selected cell data in a custom UITableViewCell?
2) What would be the best practice, so far, in Swift to implement a similar scenario?

One way would be to iterate over the superviews of the sender and see if a UITableViewCell can be found...
Let's say you created a generic UIView extension with a method that would check to see if any of its superview's was a UITableViewCell...
extension UIView {
func parentTableViewCell() -> UITableViewCell? {
var view = self
while let superview = view.superview {
if let cell = superview as? UITableViewCell {
return cell
} else {
view = superview
}
}
return nil
}
}
Then you could do the following...
if let cell = (sender as UIView).parentTableViewCell() {
let indexPath = tableView.indexPathForCell(cell)
println("The row for this cell is - \(indexPath.row)")
}
Another way would be to use the tag property of a view by setting it to an Int and then check to see what the tag of the sender was in the method.
i.e. In your tableView:cellForRowAtIndexPath: method
cell.myButton.tag = indexPath.row
Then in your doSomething method
let rowIndex = (sender as UIButton).tag
println("The row for this cell is - \(rowIndex)"

If your tableView only has one section, you can use the tag property of the button to store the indexPath.row.
In cellForRowAtIndexPath, when you set the target-action for the button, set button.tag = indexPath.row.
Then in your doSomething routine:
#IBAction doSomething(sender: UIButton) {
println("This button is from row - \(sender.tag)")
}

Related

How can i access the tag of button in CollectionView's cell contained in TableView cell

I'm working on a project in which i have a UICollectionView in UITableViewCell. My View hierarchy is like below:
TableViewCell
CollectionView
CollectionView Cell
Button(for which i want to access the know the tagValue).
I have two table view cell and in each tableView cell there is a UICollectionView And each UICollectionView contain 4 UICollectionViewCell, So i want to know the exact tagValue of my button contained within a UICollectionViewCell whenever it is pressed.
I have also given the tag value to button in cellForItem atIndexpath TableView cell class like so
cell.button.tag = indexpath.item
and i have also given selector to the button.
Try this :
Remove this line :
cell.button.tag = indexpath.item
Add this fuction
#IBAction func clickCollectionCellButton(sender : UIButton){
var cell: UICollectionViewCell? = (sender.superview?.superview as? UICollectionViewCell) //track your view hierarchy
var indexpath: IndexPath? = collectionView?.indexPath(for: cell!)
// do your additional work
}
No need to assign tag value you can directly get cell indexPath in your button exist
suppose you assign a selector to your button like this:-
#IBAction function buttonAction(_ sender:UIBUtton){
if let indexPath = viewIndexPathInCollectionView(sender , collView:pass your collectionView here){
// here you will get indexPath of your collectionView Cell
print(indexPath.item)
}
}
use this function to get index of collection view cell by passing cell item in this function
func viewIndexPathInCollectionView(_ cellItem: UIView, collView: UICollectionView) -> IndexPath? {
let pointInTable: CGPoint = cellItem.convert(cellItem.bounds.origin, to: collView)
if let cellIndexPath = collView.indexPathForItem(at: pointInTable){
return cellIndexPath
}
return nil
}

Passing indexpath tag to button inside prototype cell in tableview

I have created a tableview prototype cell in storyboard and I have added a button to cell and set its tag to indexpath.row. When I scroll my cells the scrolled cell on the top of tableview always set tag to zero instead of correct tag.
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("autoLoadReuseIndentifier", forIndexPath: indexPath)
print("indexpath :\(indexPath.row)")
cell.contentView.viewWithTag(100)?.layer.cornerRadius = 5
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
let tempDict : NSDictionary = savedCardsArray.objectAtIndex(indexPath.row) as! NSDictionary
let bankName = cell.contentView.viewWithTag(102) as! UILabel
deleteButton = cell.contentView.viewWithTag(106) as? UIButton
deleteButton?.tag = indexPath.row
deleteButton?.addTarget(self, action: "deleteCard1:", forControlEvents: UIControlEvents.TouchUpInside)
print("delete button:\(deleteButton)")
// print("indexpath delete tag :\(deleteButton.tag)")
if(self.isSetUpAutoloadSelected){
deleteButton?.hidden = true
}else{
deleteButton?.hidden = false
}
return cell;
}
Whenever I scroll the cells, delete button tag is always set to zero.
If you should go with other way so use follow code and get indexPath.
func deleteCard1(_ sender:deleteCard) {
let buttonPosition:CGPoint = sender.convert(CGPointZero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
}
I think you don't need to follow this approach because firstly you set button tag statically in storyboard and again you are change it's tag in cellforrowatindexpath so when you scroll, cell will never find button with tag 106.If you want to follow this approach then you need to create customButton and add Variable of type NSInteger or whatever you want and store indexpath.row into that variable of customButton.
Another Approach is Create CustomTableViewCell Class and create button outlet in this custom Cell class and set indexpath.row into button tag like this
CustomCellClassObject.buttonName.tag = indexPath.row
As Sumit said, it’s better to use a custom cell and create outlet for the buttons and labels, as fetching sub views using tags will be tough to maintain the code in the future.
Additionally, you don’t have to create the variable deleteButton, as I don’t see a valid purpose.
Assign tag to the button and add target in cellForRowAtIndexPath, it should work fine.

How do I pass indexPath (not indexPath.row) into my IBAction for my UITableViewCell

I am developing a to-do application. In my app, I press a checkbox button to delete a row. I write this code in order to pass the indexPath.row into my checkbox button:
cell.checkbox.tag = indexPath.row
cell.checkbox.addTarget(self, action: "checkAction:", forControlEvents: .TouchUpInside)
The first code allows me access to indexPath.row and the second one allow me to create a function for when my button is pressed. This is the function I use for when the button is pressed:
#IBAction func checkAction(sender: UIButton) {
taskMgr.removeTask(sender.tag)
tblTasks.reloadData()
}
Now, I want to add animation for when it deletes, so it doesn't look so sudden. The code I would use is this:
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
However, I only have access to indexPath.row in my checkAction function. How do I get access to the indexPath (without sacrificing indexPath.row)?
Tags can give you an erroneous row if you move rows around or add or delete rows until you reload the entire table. So, instead of using tags, you can use indexPathForRowAtPoint: in your button method to get the indexPath.
#IBAction func checkAction(sender: UIButton) {
let point = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(point)
taskMgr.removeTask(indexPath.row)
tblTasks.reloadData()
}
You can save the indexPath in the view controller:
class ViewController: UITableViewController {
var toDeletedIndexPath: NSIndexPath?
#IBAction func checkAction(sender: UIButton) {
var cell = sender
do {
cell = cell.superview
} while cell.isKindOfClass(UITableViewCell)
self.toDeletedIndexPath = self.tableView.indexPathForCell(cell)
tblTasks.reloadData()
}
}
then
tableView.deleteRowsAtIndexPaths([self.toDeletedIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
If you have only one section in table view than I would suggest you to assign tag to the instance of UIButton and the tag value should be equal to the indexPath.row
But if you have more than one section and you need to access index path than in the touch event of the button via sender than I think you should subclass UIButton and add NSIndexPath attribute. When you create instance of Custom UIButton then assign index path to the index path attribute of the custom button instance. Now when the button is clicked you can access the index path as it is the attribute of Custom UIButton.

How to access index path of button in a custom TableViewCell?

I have created a custom TableViewCell and currently have a button placed in the cell. When the button is pressed, In the tableviewcell.swift file, IBAction func gets executed. I cannot figure out how to determine the index path of the cell that the button is in that is pressed. I was trying to use the following
#IBAction func employeeAtLunch(sender: AnyObject) {
let indexPath = (self.superview as! UITableView).indexPathForCell(self)
println("indexPath?.row")
}
but I get the following error on click:
Could not cast value of type 'UITableViewWrapperView' to 'UITableView'
Any help on how to access the index path of the cell?
You are just assuming that the cell's immediate superview is the table view - wrongly. There is no particular reason why that should be so (and indeed it is not). Work with fewer assumptions! You need to keep walking up the superview chain until you do reach the table, like this:
var v : UIView = self
do { v = v.superview! } while !(v is UITableView)
Now v is the table view, and you can proceed to work out what row this is.
What I would actually do, however, is work my up, not from the cell to the table, but from the button to the cell. The technique is exactly the same:
var v : UIView = sender as! UIView
do { v = v.superview! } while !(v is UITableViewCell)
Do that the button's action method, where sender is the button. If the target of the action method is the table view controller, it has access to the table, and the problem is solved.
You could subclass UIButton in your cell with a property for its row.
class MyButton: UIButton {
var row: Int?
}
Then when you set up your table view, in the cellForRowAtIndexPath method, you set the row property:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// ...
cell.button.row = indexPath.row
// ...
}
This way when the action fires, you can get the correct row:
#IBAction func employeeAtLunch(sender: MyButton) {
if let row = sender.row {
// access the row
}
}
In your situation, I will add a tag to your button to identify in which row it is. Whenever I configure cell in the call-back cellForRowAtIndexPath, I update this tag value.
When a button clicked, the handler specifies always the button pressed. With the tag defined to that pressed button, you can know the button of which row is pressed.
#IBAction func buttonPressed(sender: AnyObject) {
//convert to UIButton
if let btn = sender as? UIButton {
let rowId = btn.tag
//do your works
}
}
If your tableview has more than 1 sections, you will have to setup the value of tags the right way.
The second solution which is better: get the position of the button in your tableView, then get indexpath of that position in your tableview:
let position = sender.convertPoint(CGPointZero, toView: self.tblMain)
let indexPath = self.tblMain.indexPathForRowAtPoint(position)

Getting UITableViewCell from UITapGestureRecognizer

I have a UITableView with multiple sections and in my cellForRowAtIndexPath method I add a UITapGestureRecognizer to the cell's imageView (each cell has a small image). I have been successful in accessing that image (in order to change it) by using:
var imageView : UIImageView! = sender.view! as UIImageView
What I need to do now, however, is access the cell's data, which I believe means I need to be able to access the cell in order to get the section and row number. Can anyone advise on how to do this? The idea is that I am changing the image to an unchecked checkbox if the task is done, but then in my data I need to actually mark that task as done.
In your function that handles the tap gesture recognizer, use tableView's indexPathForRowAtPoint function to obtain and optional index path of your touch event like so:
func handleTap(sender: UITapGestureRecognizer) {
let touch = sender.locationInView(tableView)
if let indexPath = tableView.indexPathForRowAtPoint(touch) {
// Access the image or the cell at this index path
}
}
From here, you can call cellForRowAtIndexPath to get the cell or any of its content, as you now have the index path of the tapped cell.
You can get the position of the image tapped in order to find the indexPath and from there find the cell that has that image:
var position: CGPoint = sender.locationInView(self.tableView)
var indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(position)!
var cell = self.tableView(tableView, cellForRowAtIndexPath: indexPath)
You're missing that you can use the Delegate design pattern here.
Create a protocol that tells delegates the state changes in your checkbox (imageView):
enum CheckboxState: Int {
case .Checked = 1
case .Unchecked = 0
}
protocol MyTableViewCellDelegate {
func tableViewCell(tableViewCell: MyTableViewCell, didChangeCheckboxToState state: CheckboxState)
}
And assuming you have a UITableViewCell subclass:
class MyTableViewCell: UITableViewCell {
var delegate: MyTableViewCellDelegate?
func viewDidLoad() {
// Other setup here...
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "didTapImage:")
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.userInteractionEnabled = true
}
func didTapImage(sender: AnyObject) {
// Set checked/unchecked state here
var newState: CheckboxState = .Checked // depends on whether the image shows checked or unchecked
// You pass in self as the tableViewCell so we can access it in the delegate method
delegate?.tableViewCell(self, didChangeCheckboxToState: newState)
}
}
And in your UITableViewController subclass:
class MyTableViewController: UITableViewController, MyTableViewCellDelegate {
// Other methods here (viewDidLoad, viewDidAppear, etc)...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) {
var cell = tableView.dequeueReusableCellWithIdentifier("YourIdentifier", forIndexPath: indexPath) as MyTableViewCell
// Other cell setup here...
// Assign this view controller as our MyTableViewCellDelegate
cell.delegate = self
return cell
}
override func tableViewCell(tableViewCell: MyTableViewCell, didChangeCheckboxToState state: CheckboxState) {
// You can now access your table view cell here as tableViewCell
}
}
Hope this helps!
Get tableView's cell location on a specific cell. call a function which holds the gesture of cell's Objects Like UIImage or etc.
let location = gesture.location(in: tableView)
let indexPath = tableView.indexPathForRow(at: location)
Print(indexPath.row)
Print(indexPath.section)

Resources