Swift: как получить indexpath.row при нажатии кнопки в ячейке?

У меня есть tableview с кнопками, и я хочу использовать 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)
}

Ответ 1

У 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

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

Это также позволяет избежать потенциально хрупкого подхода к навигации по иерархии представлений или использования свойства 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)")
    }
} 

Ответ 3

ОБНОВЛЕНИЕ: Получение 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) случаем, когда использование функции 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 я использовал эту технику в прошлом и был доволен результатом.

Ответ 4

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


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

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

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

Я создал расширение для UITableView, которое позволяет вам получить indexPath для любого представления, которое содержится в ячейке табличного представления. Он возвращает Optional который будет иметь значение nil, если переданное представление фактически не попадает в ячейку табличного представления. Ниже приведено полное расширение исходного файла. Вы можете просто поместить этот файл в свой проект и затем использовать включенный 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

Ответ 5

Для Swift2.1

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

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

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

Ответ 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
        }
}

Ответ 7

Решение Swift 4:

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

cell.myButton.tag = indexPath.row

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

currentCellNumber = (sender.view?.tag)!

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

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

Ответ 8

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

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

  • добавить свойство 'indexPath` в пользовательскую ячейку таблицы
  • инициализировать его в cellForRowAtIndexPath
  • переместите обработчик крана с контроллера представления на реализацию ячейки.
  • используйте шаблон делегирования, чтобы уведомить диспетчер представлений о событии крана, передав путь указателя

Ответ 9

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

Класс вашей ячейки:

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

Ответ 10

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

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

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

func editButtonPressed(_ sender: UIButton) {

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

}

Ответ 11

Я использовал метод 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);
            }   
        }
    }

Ответ 12

В 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)
}

Ответ 13

Иногда кнопка может находиться внутри другого представления UITableViewCell. В этом случае superiew.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)
}

Ответ 14

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

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

вот мой вывод (скриншот), так что смотрите это

вот мой код

  1. Создать класс модели очень просто, следуйте приведенной ниже процедуре. Создайте swift класс с именем "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.

класс 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()

}

 }