Как получить indexpath.row при активации элемента?

104

У меня есть табличное представление с кнопками, и я хочу использовать indexpath.row при нажатии на одну из них. Это то, что у меня есть сейчас, но всегда 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)
}
Винсент
источник
следует использовать IndexPathForSelectedRow () вместо точечной переменной? или где мне его использовать?
Винсент,

Ответы:

166

У giorashc почти все было в порядке со своим ответом, но он упустил из виду тот факт, что в ячейках есть дополнительный contentViewслой. Таким образом, мы должны пойти на один слой глубже:

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

let indexPath = itemTable.indexPath(for: cell)

Это связано с тем, что внутри иерархии представлений у tableView есть ячейки в качестве подпредставлений, которые впоследствии имеют свои собственные «представления содержимого», поэтому вы должны получить супервизор этого представления содержимого, чтобы получить саму ячейку. В результате, если ваша кнопка содержится в подпредставлении, а не непосредственно в представлении содержимого ячейки, вам придется пройти на несколько уровней глубже, чтобы получить к ней доступ.

Вышеупомянутый - один из таких подходов, но не обязательно лучший. Несмотря на то, что он функциональный, он предполагает детали о UITableViewCellтом, что Apple никогда не обязательно документировала, например, иерархию представлений. Это может быть изменено в будущем, и в результате приведенный выше код может вести себя непредсказуемо.

В результате вышеизложенного из соображений долговечности и надежности я рекомендую использовать другой подход. В этой ветке есть много альтернатив, и я рекомендую вам прочитать их, но мой личный фаворит:

Удерживайте свойство замыкания в своем классе ячейки, пусть метод действия кнопки вызывает это.

class MyCell: UITableViewCell {
    var button: UIButton!

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

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

Затем, когда вы создаете свою ячейку cellForRowAtIndexPath, вы можете присвоить значение своему закрытию.

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.
}

Переместив сюда свой код обработчика, вы можете воспользоваться уже существующим indexPathаргументом. Это гораздо более безопасный подход, чем тот, который указан выше, поскольку он не полагается на недокументированные черты характера.

Джейкоб Кинг
источник
2
Хорошо подмечено. Я грамотный разработчик, обещаю;) - Исправили свой ответ.
Джейкоб Кинг
12
Это неправильный способ получить ячейку с помощью кнопки. Макет ячейки менялся с годами, и такой код не будет работать, когда это произойдет. Не используйте этот подход.
rmaddy 03
11
Это плохое решение. Он предполагает подробности о UITableViewCells, с которыми Apple никогда не соглашалась. Хотя UITableViewCells имеет свойство contentView, нет гарантии, что супервизор contentView всегда будет Cell.
bpapa
1
@PintuRajput Не могли бы вы описать мне вашу иерархию представлений? Вы, вероятно, видите это, потому что ваша кнопка не является прямым подчиненным представлением представления содержимого ячейки.
Джейкоб Кинг
2
@ymutlu Полностью согласен, я сказал об этом в ответе. Я также предложил гораздо более надежное решение. Причина, по которой я оставил оригинал на месте, заключается в том, что я считаю, что лучше показать другим разработчикам проблемы с подходом, чем полностью избегать его, это не учит их ничему, что вы видите. :)
Джейкоб Кинг
61

Мой подход к такого рода проблемам - использовать протокол делегата между ячейкой и табличным представлением. Это позволяет вам сохранить обработчик кнопок в подклассе ячейки, что позволяет назначить обработчик действия касания ячейке прототипа в Interface Builder, сохраняя при этом логику обработчика кнопок в контроллере представления.

Это также позволяет избежать потенциально хрупкого подхода к навигации по иерархии представлений или использования tagсвойства, которое имеет проблемы при изменении индексов ячеек (в результате вставки, удаления или переупорядочения).

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)")
    }
} 
Paulw11
источник
buttonTappedявляется функцией делегата и находится в контроллере представления. В моем примере someButtonTappedэто метод действия в ячейке
Paulw11,
@ paulw11 У меня в ячейке нет кнопки участника, нажатой в этом методе@IBAction func someButtonTapped(sender: UIButton) { self.delegate?.buttonTapped(self) }
EI Captain v2.0
1
Это довольно хорошее решение (далеко не так плохо, как два, которые в настоящее время имеют больше голосов, используя тег, смотрящий на супервизор), но похоже, что для добавления слишком много дополнительного кода.
bpapa
2
Это правильное решение и должен быть принятым ответом. Он не злоупотребляет свойством тега, не предполагает построения ячеек (которые могут быть легко изменены Apple) и по-прежнему будет работать (без дополнительного кодирования), когда ячейки перемещаются или новые ячейки добавляются между существующими ячейками.
Robotic Cat
1
@ Paulw11 Сначала я думал, что это много кода, но оказалось, что он намного более устойчив, чем то, что я использовал раньше. Спасибо за размещение этого надежного решения.
Адриан
53

ОБНОВЛЕНИЕ : получение indexPath ячейки, содержащей кнопку (как раздел, так и строку):

Использование положения кнопки

Внутри вашего buttonTappedметода вы можете захватить позицию кнопки, преобразовать ее в координату в tableView, а затем получить indexPath строки по этой координате.

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

ПРИМЕЧАНИЕ . Иногда вы можете столкнуться с крайним случаем, когда использование функции view.convert(CGPointZero, to:self.tableView)приводит к поиску nilстроки в точке, даже если там есть ячейка tableView. Чтобы исправить это, попробуйте передать реальную координату, которая немного смещена от начала координат, например:

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

Предыдущий ответ: Использование свойства тега (возвращает только строку)

Вместо того, чтобы лазить по деревьям супервизора, чтобы захватить указатель на ячейку, которая содержит UIButton, существует более безопасный и повторяемый метод, использующий свойство button.tag, упомянутое Антонио выше, описанное в этом ответе и показанное ниже:

В cellForRowAtIndexPath:установке свойства тега:

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

Затем в buttonClicked:функции вы ссылаетесь на этот тег, чтобы получить строку indexPath, в которой расположена кнопка:

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

Я предпочитаю этот метод, так как обнаружил, что раскачивание в деревьях супервизора может быть рискованным способом разработки приложения. Кроме того, для объективного C я использовал эту технику в прошлом и был доволен результатом.

Железный Джон Бонни
источник
5
Это хороший способ сделать это, и я буду поддерживать его, чтобы немного поднять вашу репутацию, однако единственный недостаток заключается в том, что он не дает доступа к indexPath.section, если это также необходимо. Хотя отличный ответ!
Джейкоб Кинг
Спасибо, Джейкоб! Я ценю репутационную карму. Если вы хотите получить indexPath.sectionв дополнение к indexPath.row(без сброса свойства тега как indexPath.section), cellForRowAtIndexPath:вы можете просто изменить тег на button.tag = indexPath, а затем в buttonClicked:функции, к которой вы можете получить доступ, используя sender.tag.rowи sender.tag.section.
Айрон Джон Бонни
1
Это новая функция, потому что я уверен, что помню свойство тега типа Int, а не типа AnyObject, если это не изменилось в Swift 2.3?
Джейкоб Кинг,
@JacobKing, ты прав! Жалко, что при написании этого комментария я полностью выделил интервал и подумал, что это тег типа AnyObject. Дерп - не обращай на меня внимания. Было бы полезно, если бы вы могли передать indexPath как тег ...
Iron John Bonney,
3
Тоже не очень хороший подход. Во-первых, он будет работать только в представлениях таблиц, где есть один раздел.
bpapa
16

Используйте расширение для UITableView, чтобы получить ячейку для любого представления:


Ответ @ Paulw11 о настройке пользовательского типа ячейки со свойством делегата, который отправляет сообщения в табличное представление, - хороший способ, но для его настройки требуется определенный объем работы.

Я думаю, что ходить по иерархии представлений ячейки таблицы в поисках ячейки - плохая идея. Он хрупкий - если позже вы поместите кнопку в представление для целей макета, этот код, скорее всего, сломается.

Использование тегов просмотра также ненадежно. Вы должны не забыть настроить теги при создании ячейки, и если вы используете этот подход в контроллере представления, который использует теги представления для другой цели, вы можете иметь повторяющиеся номера тегов, и ваш код может не работать должным образом.

Я создал расширение для UITableView, которое позволяет вам получить indexPath для любого представления, содержащегося в ячейке представления таблицы. Он возвращает значение Optionalnil, если переданное представление фактически не попадает в ячейку табличного представления. Ниже представлен полный исходный файл расширения. Вы можете просто поместить этот файл в свой проект, а затем использовать включенный indexPathForView(_:)метод для поиска indexPath, содержащего любое представление.

//
//  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
    }
}

Чтобы использовать его, вы можете просто вызвать метод в IBAction для кнопки, содержащейся в ячейке:

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

(Обратите внимание, что indexPathForView(_:)функция будет работать только в том случае, если переданный ей объект представления содержится в ячейке, которая в данный момент отображается на экране. Это разумно, поскольку представление, которое не отображается на экране, на самом деле не принадлежит конкретному indexPath; скорее всего, быть назначенным другому indexPath, когда содержащая его ячейка перерабатывается.)

РЕДАКТИРОВАТЬ:

Вы можете скачать рабочий демонстрационный проект, в котором используется указанное выше расширение, из Github: TableViewExtension.git

Дункан С
источник
Спасибо, я использовал расширение, чтобы получить indexPath текстового представления в ячейке - отлично сработало.
Джереми Эндрюс
9

Для Swift2.1

Я нашел способ сделать это, надеюсь, это поможет.

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

    guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
        fatalError("can't find point in tableView")
    }
спецавик
источник
Что это значит, если ошибка запускается? По каким причинам он не может найти точку в tableView?
OOProg
Этот (или аналогичный, с использованием методов преобразования UIView) должен быть принятым ответом. Не знаю, почему сейчас он №4, поскольку он не делает предположений о частной иерархии табличного представления, не использует свойство тега (почти всегда плохая идея) и не требует большого количества дополнительного кода.
bpapa
9

Решение Swift 4:

У вас есть кнопка (myButton) или любое другое представление в ячейке. Назначьте тег в cellForRowAt как это

cell.myButton.tag = indexPath.row

Теперь у вас tapFunction или любой другой. Извлеките его вот так и сохраните в локальной переменной.

currentCellNumber = (sender.view?.tag)!

После этого вы можете использовать в любом месте этот currentCellNumber, чтобы получить indexPath.row выбранной кнопки.

Наслаждайтесь!

Саджид Зеб
источник
Этот подход работает, но, как упоминалось в моем ответе, теги просмотра хрупкие. Например, простой целочисленный тег не будет работать для представления таблицы с разделами. (IndexPath, это 2 целых числа.) Мой подход всегда будет работать, и нет необходимости устанавливать тег в кнопку (или другое интерактивное представление).
Дункан К.
6

В Swift 4 просто используйте это:

func buttonTapped(_ sender: UIButton) {
        let buttonPostion = sender.convert(sender.bounds.origin, to: tableView)

        if let indexPath = tableView.indexPathForRow(at: buttonPostion) {
            let rowIndex =  indexPath.row
        }
}
ДИПАК КУМАР
источник
Должен быть выбран самый чистый ответ. Единственное, что следует отметить, tableView- это переменная выхода, на которую необходимо ссылаться до того, как этот ответ заработает.
10000RubyPools
Работайте как шарм !!
Parthpatel1105
4

Очень просто получить Index Path Swift 4, 5

let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: "buttonTapped:", forControlEvents: 
UIControlEvents.TouchUpInside)

Как получить IndexPath внутри Btn Click:

func buttonTapped(_ sender: UIButton) {
     print(sender.tag)  
}
М Муртеза
источник
3

Поскольку отправителем обработчика событий является сама кнопка, я бы использовал tagсвойство кнопки для хранения индекса, инициализированного в cellForRowAtIndexPath.

Но приложив немного больше усилий, я бы поступил совершенно по-другому. Если вы используете настраиваемую ячейку, я бы подошел к проблеме следующим образом:

  • добавить свойство indexPath в ячейку настраиваемой таблицы
  • инициализировать его в cellForRowAtIndexPath
  • переместите обработчик касания из контроллера представления в реализацию ячейки
  • использовать шаблон делегирования, чтобы уведомить контроллер представления о событии касания, передав путь индекса
Антонио
источник
Антонио, у меня есть индивидуальная ячейка, и я хотел бы сделать это по-вашему. Однако это не работает. Я хочу, чтобы мой код «проведите пальцем по экрану, чтобы открыть кнопку удаления» был запущен, а это метод tableView commitEditingStyle. Я удалил этот код из класса mainVC и поместил его в класс customCell, но теперь код больше не работает. Что мне не хватает?
Дэйв Джи
Я думаю, что это лучший способ получить indexPath ячейки с x секциями, однако я не вижу необходимости в маркерах 3 и 4 в подходе MVC
Эдвард
2

Увидев предложение Paulw11 об использовании обратного вызова делегата, я хотел немного подробнее остановиться на нем / выдвинуть другое аналогичное предложение. Если вы не хотите использовать шаблон делегата, вы можете быстро использовать закрытие следующим образом:

Ваш класс ячейки:

class Cell: UITableViewCell {
    @IBOutlet var button: UIButton!

    var buttonAction: ((sender: AnyObject) -> Void)?

    @IBAction func buttonPressed(sender: AnyObject) {
        self.buttonAction?(sender)
    }
}

Ваш cellForRowAtIndexPathметод:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
    cell.buttonAction = { (sender) in
        // Do whatever you want from your button here.
    }
    // OR
    cell.buttonAction = buttonPressed // <- Method on the view controller to handle button presses.
}
Джейкоб Кинг
источник
2

Я нашел очень простой способ управления любой ячейкой в ​​tableView и collectionView с помощью класса Model, и это отлично работает.

Сейчас действительно есть гораздо лучший способ справиться с этим. Это будет работать для управления ячейкой и значением.

Вот мой результат (снимок экрана), так что посмотрите это:

введите описание изображения здесь

  1. Создать класс модели очень просто , следуйте приведенной ниже процедуре. Создайте быстрый класс с именем RNCheckedModel, напишите код, как показано ниже.
class RNCheckedModel: NSObject {

    var is_check = false
    var user_name = ""

    }
  1. создайте свой класс ячейки
class InviteCell: UITableViewCell {

    @IBOutlet var imgProfileImage: UIImageView!
    @IBOutlet var btnCheck: UIButton!
    @IBOutlet var lblName: UILabel!
    @IBOutlet var lblEmail: UILabel!
    }
  1. и, наконец, используйте класс модели в своем UIViewController, когда вы используете свой UITableView .
    class RNInviteVC: UIViewController, UITableViewDelegate, UITableViewDataSource {


    @IBOutlet var inviteTableView: UITableView!
    @IBOutlet var btnInvite: UIButton!

    var checkArray : NSMutableArray = NSMutableArray()
    var userName : NSMutableArray = NSMutableArray()

    override func viewDidLoad() {
        super.viewDidLoad()
        btnInvite.layer.borderWidth = 1.5
        btnInvite.layer.cornerRadius = btnInvite.frame.height / 2
        btnInvite.layer.borderColor =  hexColor(hex: "#512DA8").cgColor

        var userName1 =["Olivia","Amelia","Emily","Isla","Ava","Lily","Sophia","Ella","Jessica","Mia","Grace","Evie","Sophie","Poppy","Isabella","Charlotte","Freya","Ruby","Daisy","Alice"]


        self.userName.removeAllObjects()
        for items in userName1 {
           print(items)


            let model = RNCheckedModel()
            model.user_name = items
            model.is_check = false
            self.userName.add(model)
        }
      }
     @IBAction func btnInviteClick(_ sender: Any) {

    }
       func tableView(_ tableView: UITableView, numberOfRowsInSection 
       section: Int) -> Int {
        return userName.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: InviteCell = inviteTableView.dequeueReusableCell(withIdentifier: "InviteCell", for: indexPath) as! InviteCell

        let image = UIImage(named: "ic_unchecked")
        cell.imgProfileImage.layer.borderWidth = 1.0
        cell.imgProfileImage.layer.masksToBounds = false
        cell.imgProfileImage.layer.borderColor = UIColor.white.cgColor
        cell.imgProfileImage.layer.cornerRadius =  cell.imgProfileImage.frame.size.width / 2
        cell.imgProfileImage.clipsToBounds = true

        let model = self.userName[indexPath.row] as! RNCheckedModel
        cell.lblName.text = model.user_name

        if (model.is_check) {
            cell.btnCheck.setImage(UIImage(named: "ic_checked"), for: UIControlState.normal)
        }
        else {
            cell.btnCheck.setImage(UIImage(named: "ic_unchecked"), for: UIControlState.normal)
        }

        cell.btnCheck.tag = indexPath.row
        cell.btnCheck.addTarget(self, action: #selector(self.btnCheck(_:)), for: .touchUpInside)

        cell.btnCheck.isUserInteractionEnabled = true

    return cell

    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80

    }

    @objc func btnCheck(_ sender: UIButton) {

        let tag = sender.tag
        let indexPath = IndexPath(row: tag, section: 0)
        let cell: InviteCell = inviteTableView.dequeueReusableCell(withIdentifier: "InviteCell", for: indexPath) as! InviteCell

        let model = self.userName[indexPath.row] as! RNCheckedModel

        if (model.is_check) {

            model.is_check = false
            cell.btnCheck.setImage(UIImage(named: "ic_unchecked"), for: UIControlState.normal)

            checkArray.remove(model.user_name)
            if checkArray.count > 0 {
                btnInvite.setTitle("Invite (\(checkArray.count))", for: .normal)
                print(checkArray.count)
                UIView.performWithoutAnimation {
                    self.view.layoutIfNeeded()
                }
            } else {
                btnInvite.setTitle("Invite", for: .normal)
                UIView.performWithoutAnimation {
                    self.view.layoutIfNeeded()
                }
            }

        }else {

            model.is_check = true
            cell.btnCheck.setImage(UIImage(named: "ic_checked"), for: UIControlState.normal)

            checkArray.add(model.user_name)
            if checkArray.count > 0 {
                btnInvite.setTitle("Invite (\(checkArray.count))", for: .normal)
                UIView.performWithoutAnimation {
                self.view.layoutIfNeeded()
                }
            } else {
                 btnInvite.setTitle("Invite", for: .normal)
            }
        }

        self.inviteTableView.reloadData()
    }

    func hexColor(hex:String) -> UIColor {
        var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()

        if (cString.hasPrefix("#")) {
            cString.remove(at: cString.startIndex)
        }

        if ((cString.count) != 6) {
            return UIColor.gray
        }

        var rgbValue:UInt32 = 0
        Scanner(string: cString).scanHexInt32(&rgbValue)

        return UIColor(
            red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
            blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
            alpha: CGFloat(1.0)
        )
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()

    }

     }
Пареш Мангукия
источник
1

Я использовал метод convertPoint, чтобы получить точку из tableview и передать эту точку методу indexPathForRowAtPoint, чтобы получить indexPath

 @IBAction func newsButtonAction(sender: UIButton) {
        let buttonPosition = sender.convertPoint(CGPointZero, toView: self.newsTableView)
        let indexPath = self.newsTableView.indexPathForRowAtPoint(buttonPosition)
        if indexPath != nil {
            if indexPath?.row == 1{
                self.performSegueWithIdentifier("alertViewController", sender: self);
            }   
        }
    }
Авиджит Нагаре
источник
1

Попробуйте использовать #selector для вызова действия IB. В cellforrowatindexpath

            cell.editButton.tag = indexPath.row
        cell.editButton.addTarget(self, action: #selector(editButtonPressed), for: .touchUpInside)

Таким образом, вы можете получить доступ к indexpath внутри метода editButtonPressed

func editButtonPressed(_ sender: UIButton) {

print(sender.tag)//this value will be same as indexpath.row

}
Винит Кришнан
источник
Наиболее подходящий ответ
Амаленду Кар
Нет, теги будут отключены, когда пользователь добавляет или удаляет ячейки.
Коэн
1

В моем случае у меня есть несколько разделов, и индекс раздела и строки жизненно важен, поэтому в таком случае я просто создал свойство в UIButton, которое я установил indexPath ячейки следующим образом:

fileprivate struct AssociatedKeys {
    static var index = 0
}

extension UIButton {

    var indexPath: IndexPath? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.index) as? IndexPath
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.index, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

Затем установите свойство в cellForRowAt следующим образом:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
    cell.button.indexPath = indexPath
}

Затем в handleTapAction вы можете получить indexPath следующим образом:

@objc func handleTapAction(_ sender: UIButton) {
    self.selectedIndex = sender.indexPath

}
DvixExtract
источник
1

Swift 4 и 5

Метод 1 с использованием делегата протокола

Например, у вас есть UITableViewCellимяMyCell

class MyCell: UITableViewCell {
    
    var delegate:MyCellDelegate!
    
    @IBAction private func myAction(_ sender: UIButton){
        delegate.didPressButton(cell: self)
    }
}

Теперь создайте protocol

protocol MyCellDelegate {
    func didPressButton(cell: UITableViewCell)
}

Следующим шагом создайте расширение UITableView

extension UITableView {
    func returnIndexPath(cell: UITableViewCell) -> IndexPath?{
        guard let indexPath = self.indexPath(for: cell) else {
            return nil
        }
        return indexPath
    }
}

В вашей UIViewControllerреализации протоколMyCellDelegate

class ViewController: UIViewController, MyCellDelegate {
     
    func didPressButton(cell: UITableViewCell) {
        if let indexpath = self.myTableView.returnIndexPath(cell: cell) {
              print(indexpath)
        }
    }
}

Метод 2 с использованием замыканий

В UIViewController

override func viewDidLoad() {
        super.viewDidLoad()
       //using the same `UITableView extension` get the IndexPath here
        didPressButton = { cell in
            if let indexpath = self.myTableView.returnIndexPath(cell: cell) {
                  print(indexpath)
            }
        }
    }
 var didPressButton: ((UITableViewCell) -> Void)

class MyCell: UITableViewCell {

    @IBAction private func myAction(_ sender: UIButton){
        didPressButton(self)
    }
}

Примечание: - если вы хотите получить UICollectionViewindexPath, вы можете использовать это UICollectionView extensionи повторить вышеуказанные шаги

extension UICollectionView {
    func returnIndexPath(cell: UICollectionViewCell) -> IndexPath?{
        guard let indexPath = self.indexPath(for: cell) else {
            return nil
        }
        return indexPath
    }
}
Рашид Латиф
источник
0

В Swift 3. Также используются операторы защиты, избегая длинной цепочки скобок.

func buttonTapped(sender: UIButton) {
    guard let cellInAction = sender.superview as? UITableViewCell else { return }
    guard let indexPath = tableView?.indexPath(for: cellInAction) else { return }

    print(indexPath)
}
Шон
источник
Это не будет работать. Супервизор кнопки не будет ячейкой.
rmaddy 03
Это действительно работает. Единственное, чего вы должны быть осторожны, это то, что стек представлений у всех разный. Это может быть sender.superview, sender.superview.superview или sender.superview.superview.superview. Но это действительно хорошо работает.
Шон
0

Иногда кнопка может находиться внутри другого представления UITableViewCell. В этом случае superview.superview может не выдавать объект ячейки и, следовательно, indexPath будет равен нулю.

В этом случае мы должны продолжать поиск супервизора, пока не получим объект ячейки.

Функция для получения объекта ячейки с помощью супервизора

func getCellForView(view:UIView) -> UITableViewCell?
{
    var superView = view.superview

    while superView != nil
    {
        if superView is UITableViewCell
        {
            return superView as? UITableViewCell
        }
        else
        {
            superView = superView?.superview
        }
    }

    return nil
}

Теперь мы можем получить indexPath при нажатии кнопки, как показано ниже.

@IBAction func tapButton(_ sender: UIButton)
{
    let cell = getCellForView(view: sender)
    let indexPath = myTabelView.indexPath(for: cell)
}
Тина Нат Пол
источник
0
// CustomCell.swift

protocol CustomCellDelegate {
    func tapDeleteButton(at cell: CustomCell)
}

class CustomCell: UICollectionViewCell {
    
    var delegate: CustomCellDelegate?
    
    fileprivate let deleteButton: UIButton = {
        let button = UIButton(frame: .zero)
        button.setImage(UIImage(named: "delete"), for: .normal)
        button.addTarget(self, action: #selector(deleteButtonTapped(_:)), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    @objc fileprivate func deleteButtonTapped(_sender: UIButton) {
        delegate?.tapDeleteButton(at: self)
    }
    
}

//  ViewController.swift

extension ViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier, for: indexPath) as? CustomCell else {
            fatalError("Unexpected cell instead of CustomCell")
        }
        cell.delegate = self
        return cell
    }

}

extension ViewController: CustomCellDelegate {

    func tapDeleteButton(at cell: CustomCell) {
        // Here we get the indexPath of the cell what we tapped on.
        let indexPath = collectionView.indexPath(for: cell)
    }

}
iAleksandr
источник