I have a tableview with buttons and I want to use the indexpath.row when one of them is tapped. This is what I currently have, but it always is 0
var point = Int()
func buttonPressed(sender: AnyObject) {
let pointInTable: CGPoint = sender.convertPoint(sender.bounds.origin, toView: self.tableView)
let cellIndexPath = self.tableView.indexPathForRowAtPoint(pointInTable)
println(cellIndexPath)
point = cellIndexPath!.row
println(point)
}
Sometimes button may be inside of another view of UITableViewCell. In that case superview.superview may not give the cell object and hence the indexPath will be nil.
In that case we should keep finding the superview until we get the cell object.
Function to get cell object by superview
Now we can get indexPath on button tap as below
UPDATE: Getting the indexPath of the cell containing the button (both section and row):
Using Button Position
Inside of your
buttonTapped
method, you can grab the button's position, convert it to a coordinate in the tableView, then get the indexPath of the row at that coordinate.NOTE: Sometimes you can run into an edge case when using the function
view.convert(CGPointZero, to:self.tableView)
results in findingnil
for a row at a point, even though there is a tableView cell there. To fix this, try passing a real coordinate that is slightly offset from the origin, such as:Previous Answer: Using Tag Property (only returns row)
Rather than climbing into the superview trees to grab a pointer to the cell that holds the UIButton, there is a safer, more repeatable technique utilizing the button.tag property mentioned by Antonio above, described in this answer, and shown below:
In
cellForRowAtIndexPath:
you set the tag property:Then, in the
buttonClicked:
function, you reference that tag to grab the row of the indexPath where the button is located:I prefer this method since I've found that swinging in the superview trees can be a risky way to design an app. Also, for objective-C I've used this technique in the past and have been happy with the result.
giorashc almost had it with his answer, but he overlooked the fact that cell's have an extra
contentView
layer. Thus, we have to go one layer deeper:This is because within the view hierarchy a tableView has cells as subviews which subsequently have their own 'content views' this is why you must get the superview of this content view to get the cell itself. As a result of this, if your button is contained in a subview rather than directly into the cell's content view, you'll have to go however many layers deeper to access it.
The above is one such approach, but not necessarily the best approach. Whilst it is functional, it assumes details about a
UITableViewCell
that Apple have never necessarily documented, such as it's view hierarchy. This could be changed in the future, and the above code may well behave unpredictably as a result.As a result of the above, for longevity and reliability reasons, I recommend adopting another approach. There are many alternatives listed in this thread, and I encourage you to read down, but my personal favourite is as follows:
Hold a property of a closure on your cell class, have the button's action method invoke this.
Then, when you create your cell in
cellForRowAtIndexPath
, you can assign a value to your closure.By moving your handler code here, you can take advantage of the already present
indexPath
argument. This is a much safer approach that the one listed above as it doesn't rely on undocumented traits.Since the sender of the event handler is the button itself, I'd use the button's
tag
property to store the index, initialized incellForRowAtIndexPath
.But with a little more work I'd do in a completely different way. If you are using a custom cell, this is how I would approach the problem:
cellForRowAtIndexPath
Use an extension to UITableView to fetch the cell for any view:
@Paulw11's answer of setting up a custom cell type with a delegate property that sends messages to the table view is a good way to go, but it requires a certain amount of work to set up.
I think walking the table view cell's view hierarchy looking for the cell is a bad idea. It is fragile - if you later enclose your button in a view for layout purposes, that code is likely to break.
Using view tags is also fragile. You have to remember to set up the tags when you create the cell, and if you use that approach in a view controller that uses view tags for another purpose you can have duplicate tag numbers and your code can fail to work as expected.
I have created an extension to UITableView that lets you get the indexPath for any view that is contained in a table view cell. It returns an
Optional
that will be nil if the view passed in actually does not fall within a table view cell. Below is the extension source file in it's entirety. You can simply put this file in your project and then use the includedindexPathForView(_:)
method to find the indexPath that contains any view.To use it, you can simply call the method in the IBAction for a button that's contained in a cell:
(Note that the
indexPathForView(_:)
function will only work if the view object it's passed is contained by a cell that's currently on-screen. That's reasonable, since a view that is not on-screen doesn't actually belong to a specific indexPath; it's likely to be assigned to a different indexPath when it's containing cell is recycled.)EDIT:
You can download a working demo project that uses the above extension from Github: TableViewExtension.git
In Swift 3. Also used guard statements, avoiding a long chain of braces.