Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the indexpath.row when an element is activated?

People also ask

What is IndexPath row?

Index paths describe an item's position inside a table view or collection view, storing both its section and its position inside that section. For example, the first row in a table would have section 0, row 0, whereas the eighth row in the fourth section would have section 3, row 7.

How do I get last IndexPath in Swift?

You can get the indexPath of the last row in last section like this. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(numberOfRowsInLastSection - 1) inSection:(numberOfSections - 1)];

What is IndexPath section?

An index number identifying a row in a UITableView object in a section identified by section. section.


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:

guard let cell = sender.superview?.superview as? YourCellClassHere else {
    return // or fatalError() or whatever
}

let indexPath = itemTable.indexPath(for: cell)

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.

class MyCell: UITableViewCell {
    var button: UIButton!

    var buttonAction: ((Any) -> Void)?

    @objc func buttonPressed(sender: Any) {
        self.buttonAction?(sender)
    }
}

Then, when you create your cell in cellForRowAtIndexPath, you can assign a value to your closure.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! MyCell
    cell.buttonAction = { sender in
        // Do whatever you want from your button here.
    }
    // OR
    cell.buttonAction = buttonPressed(closure: buttonAction, indexPath: indexPath) // <- Method on the view controller to handle button presses.
}

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.


My approach to this sort of problem is to use a delegate protocol between the cell and the tableview. This allows you to keep the button handler in the cell subclass, which enables you to assign the touch up action handler to the prototype cell in Interface Builder, while still keeping the button handler logic in the view controller.

It also avoids the potentially fragile approach of navigating the view hierarchy or the use of the tag property, which has issues when cells indexes change (as a result of insertion, deletion or reordering)

CellSubclass.swift

protocol CellSubclassDelegate: class {
    func buttonTapped(cell: CellSubclass)
}

class CellSubclass: UITableViewCell {

@IBOutlet var someButton: UIButton!

weak var delegate: CellSubclassDelegate?

override func prepareForReuse() {
    super.prepareForReuse()
    self.delegate = nil
}

@IBAction func someButtonTapped(sender: UIButton) {
    self.delegate?.buttonTapped(self)
}

ViewController.swift

class MyViewController: UIViewController, CellSubclassDelegate {

    @IBOutlet var tableview: UITableView!

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CellSubclass

        cell.delegate = self

        // Other cell setup

    } 

    //  MARK: CellSubclassDelegate

    func buttonTapped(cell: CellSubclass) {
        guard let indexPath = self.tableView.indexPathForCell(cell) else {
            // Note, this shouldn't happen - how did the user tap on a button that wasn't on screen?
            return
        }

        //  Do whatever you need to do with the indexPath

        print("Button tapped on row \(indexPath.row)")
    }
} 

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.

func buttonTapped(_ sender:AnyObject) {
    let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:self.tableView)
    let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
}

NOTE: Sometimes you can run into an edge case when using the function view.convert(CGPointZero, to:self.tableView) results in finding nil 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:

let buttonPosition:CGPoint = sender.convert(CGPoint.init(x: 5.0, y: 5.0), to:self.tableView)

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:

button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)

Then, in the buttonClicked: function, you reference that tag to grab the row of the indexPath where the button is located:

func buttonClicked(sender:UIButton) {
    let buttonRow = sender.tag
}

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.


Use an extension to UITableView to fetch the cell that contains 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 included indexPathForView(_:) method to find the indexPath that contains any view.

//
//  UITableView+indexPathForView.swift
//  TableViewExtension
//
//  Created by Duncan Champney on 12/23/16.
//  Copyright © 2016-2017 Duncan Champney.
//  May be used freely in for any purpose as long as this 
//  copyright notice is included.

import UIKit

public extension UITableView {
  
  /**
  This method returns the indexPath of the cell that contains the specified view
   
   - Parameter view: The view to find.
   
   - Returns: The indexPath of the cell containing the view, or nil if it can't be found
   
  */
  
    func indexPathForView(_ view: UIView) -> IndexPath? {
        let center = view.center
        let viewCenter = self.convert(center, from: view.superview)
        let indexPath = self.indexPathForRow(at: viewCenter)
        return indexPath
    }
}

To use it, you can simply call the method in the IBAction for a button that's contained in a cell:

func buttonTapped(_ button: UIButton) {
  if let indexPath = self.tableView.indexPathForView(button) {
    print("Button tapped at indexPath \(indexPath)")
  }
  else {
    print("Button indexPath not found")
  }
}

(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


Solution:

You have a button (myButton) or any other view in cell. Assign tag in cellForRowAt like this

cell.myButton.tag = indexPath.row

Now in you tapFunction or any other. Fetch it out like this and save it in a local variable.

currentCellNumber = (sender.view?.tag)!

After this you can use anywhere this currentCellNumber to get the indexPath.row of selected button.

Enjoy!


For Swift2.1

I found a way to do it, hopefully, it'll help.

let point = tableView.convertPoint(CGPoint.zero, fromView: sender)

    guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
        fatalError("can't find point in tableView")
    }