31

I have 2 files.

  • myTableViewController.swift
  • myTableCell.swift

Can I get the indexPath.row in myTabelCell.swift function?

Here is myTableCell.swift

import UIKit
import Parse
import ActiveLabel

class myTableCell : UITableViewCell {

    //Button
    @IBOutlet weak var commentBtn: UIButton!
    @IBOutlet weak var likeBtn: UIButton!
    @IBOutlet weak var moreBtn: UIButton!


    override func awakeFromNib() {
        super.awakeFromNib()


    }

    @IBAction func likeBtnTapped(_ sender: AnyObject) {

        //declare title of button
        let title = sender.title(for: UIControlState())

        //I want get indexPath.row in here!

    }

Here is myTableViewController.swift

class myTableViewController: UITableViewController {

    //Default func
    override func viewDidLoad() {
        super.viewDidLoad()

        //automatic row height
        tableView.estimatedRowHeight = 450
        tableView.rowHeight = UITableViewAutomaticDimension



    }

 // cell config
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


        //define cell
        let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexPath) as! myTableCell



 }

As you can see... I'm trying to get indexPath.row in myTableCell, liktBtnTapped function.

Could you let me know how can I access or get IndexPath.row?

Shawn Baek
  • 1,928
  • 3
  • 20
  • 34

9 Answers9

45

I have created a UIResponder extension with a recursive method that you can use in any UIView (which inherits from UIResponder) to find a parent view of a specific type.

import UIKit

extension UIResponder {
    /**
     * Returns the next responder in the responder chain cast to the given type, or
     * if nil, recurses the chain until the next responder is nil or castable.
     */
    func next<U: UIResponder>(of type: U.Type = U.self) -> U? {
        return self.next.flatMap({ $0 as? U ?? $0.next() })
    }
}

Using this, we can extend UITableViewCell with some convenient read-only computed properties for the table view and index path of the cell.

extension UITableViewCell {
    var tableView: UITableView? {
        return self.next(of: UITableView.self)
    }

    var indexPath: IndexPath? {
        return self.tableView?.indexPath(for: self)
    }
}

Here is how you could use it in your example:

@IBAction func likeBtnTapped(_ sender: AnyObject) {
    //declare title of button
    let title = sender.title(for: UIControlState())

    //I want get indexPath.row in here!
    self.indexPath.flatMap { print($0) }
}
Callam
  • 11,409
  • 2
  • 34
  • 32
  • Seems awesome extension. Would you mind puting code how to use? – Tarvo Mäesepp Nov 05 '16 at 11:37
  • Just create a new file called `UITableViewCell+IndexPath.swift` and append the code above. Then when inside your cell, you can get the index path using `self.indexPath`. – Callam Nov 05 '16 at 11:39
  • This code is making an assumption that may or may not be true depending on the version of iOS. You should not assume that the a table view cell's superview is the table view itself. Never make assumptions about the private subview structure of a `UITableView`. – rmaddy Sep 17 '17 at 15:21
  • 1
    @rmaddy That is correct, however, you can assume the table view cell is at least a descendant of the table view. I have updated my answer to find the table view using the `UIResponder` next property. Ultimately the best solution is to simply pass the index path to the cell when dequeuing it but everyone loves a bit a of magic. – Callam Sep 17 '17 at 19:43
  • 2
    @Callam No, it is not best to pass the index path to the cell. That's actually the worst solution. It fails miserably if rows can be inserted, removed, or moved in the table view. – rmaddy Sep 17 '17 at 19:46
  • @rmaddy Yes, now thinking about it - please disregard that part of my comment. That was the whole point of getting the table view in the cell. Would you agree that my update improves the stability? – Callam Sep 17 '17 at 19:47
  • That's looks like it should work. But what's the point of having both sets of code in your answer? Just edit your original code with the fix. – rmaddy Sep 17 '17 at 19:51
26

Swift 4+

Try this inside your cell.

func getIndexPath() -> IndexPath? {
    guard let superView = self.superview as? UITableView else {
        print("superview is not a UITableView - getIndexPath")
        return nil
    }
    indexPath = superView.indexPath(for: self)
    return indexPath
}
Rakesha Shastri
  • 11,053
  • 3
  • 37
  • 50
9

Easy.. You can do like this inside button action:

let section = 0
let row = sender.tag
let indexPath = IndexPath(row: row, section: section)
let cell: myTableCell = self.feedTableView.cellForRow(at: indexPath) as! myTableCell

And afterwards in cellForRowAtIndexPath:

// add the row as the tag
cell.button.tag = indexPath.row
Tarvo Mäesepp
  • 4,477
  • 3
  • 44
  • 92
6

Another Approach for Swift 4.2 and not assuming Superview will be always a tableview

extension UITableViewCell{

    var tableView:UITableView?{
        return superview as? UITableView
    }

    var indexPath:IndexPath?{
        return tableView?.indexPath(for: self)
    }

}

Usage example

@IBAction func checkBoxAction(_ sender: UIButton) {

        guard let indexPath = indexPath else { return }

        sender.isSelected = !sender.isSelected
        myCustomCellDelegate?.checkBoxTableViewCell(didSelectCheckBox: sender.isSelected, for: indexPath)

}
Reimond Hill
  • 4,278
  • 40
  • 52
3

Swift 4.1. Here I created function to get IndexPath. Just pass your UIView(UIButton,UITextField etc) and UITableView object to get IndexPath.

func getIndexPathFor(view: UIView, tableView: UITableView) -> IndexPath? {

            let point = tableView.convert(view.bounds.origin, from: view)
            let indexPath = tableView.indexPathForRow(at: point)
            return indexPath
 }
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
1

Create a property indexPath in the cell class and set it in cellForRowAtIndexPath when the cell is reused.

But there is a caveat: Some table view methods to rearrange the cells don't call cellForRowAtIndexPath. You have to consider this case.

But if you use always only reloadData() it's safe and pretty easy.


Another way is to put the code regarding controlling things back in the controller class and run it via callback closures capturing the index path.

vadian
  • 274,689
  • 30
  • 353
  • 361
1

Heres another way of doing it

import UIKit
import Parse
import ActiveLabel

class myTableCell : UITableViewCell {

//Button
@IBOutlet weak var commentBtn: UIButton!
@IBOutlet weak var likeBtn: UIButton!
@IBOutlet weak var moreBtn: UIButton!


override func awakeFromNib() {
    super.awakeFromNib()


}


}


class myTableViewController: UITableViewController {

//Default func
//assuming you have an array for your table data source
var arrayOfTitles = [String]()
override func viewDidLoad() {
    super.viewDidLoad()

    //automatic row height
    tableView.estimatedRowHeight = 450
    tableView.rowHeight = UITableViewAutomaticDimension



}

// cell config
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


//define cell
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexPath) as! myTableCell
cell.commentBtn.tag = indexPath.row
cell.commentBtn.addTarget(self, action: #selector(likeBtnTapped(_:), forControlEvents:.TouchUpInside)


//cell config end


@IBAction func likeBtnTapped(sender: UIButton) {
let btn = sender
let indexP = NSIndexPath(forItem: btn.tag, inSection: 0)
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexP) as! myTableCell

    //I want get indexPath.row in here!
    let title = arrayOfTitles[indexP.row]

    //declare title of button
    cell.commentBtn.setTitle(title, forState: UIControlState.Normal)



}


}
1

My solution was subclassing UITableViewCell, so can add IndexPath property. assign custom class for table view cell in storyboard. assign IndexPath value when rowAtIndexPath called.

class MyTableViewCell: UITableViewCell {

    var indexPath: IndexPath?
}

custom class

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    var cell = tableView.dequeueReusableCell(withIdentifier: "cellid1", for: indexPath)
    (cell as? MyTableViewCell)?.indexPath = indexPath
    return cell
}
ossamacpp
  • 656
  • 5
  • 11
0

Swift 5:

        if 
            let collectionView = superview as? UICollectionView, 
            let index = collectionView.indexPath(for: self) 
        {
            // stuff
        }
Ivan
  • 1,320
  • 16
  • 26