Como obter o indexpath.row quando um elemento é ativado?

104

Eu tenho um tableview com botões e quero usar o indexpath.row quando um deles é tocado. Isso é o que eu tenho atualmente, mas sempre é 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)
}
Vincent
fonte
devo usar IndexPathForSelectedRow () em vez da variável de ponto? ou onde devo usá-lo?
Vincent

Respostas:

164

Giorashc quase acertou a resposta, mas não percebeu o fato de que as células têm uma contentViewcamada extra . Portanto, temos que ir uma camada mais a fundo:

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

let indexPath = itemTable.indexPath(for: cell)

Isso ocorre porque dentro da hierarquia de visualização, uma tableView possui células como subvisualizações que subsequentemente têm suas próprias 'visualizações de conteúdo', é por isso que você deve obter a supervisão dessa visualização de conteúdo para obter a própria célula. Como resultado disso, se o botão estiver contido em uma subvisualização em vez de diretamente na visualização do conteúdo da célula, você terá que ir muitas camadas mais fundo para acessá-lo.

A abordagem acima é uma dessas abordagens, mas não necessariamente a melhor abordagem. Embora seja funcional, assume detalhes sobre umUITableViewCell que a Apple nunca necessariamente documentou, como sua hierarquia de visualização. Isso pode ser alterado no futuro e, como resultado, o código acima pode se comportar de maneira imprevisível.

Como resultado do acima, por razões de longevidade e confiabilidade, recomendo adotar outra abordagem. Existem muitas alternativas listadas neste tópico, e eu encorajo você a ler, mas minha favorita é a seguinte:

Mantenha uma propriedade de um fechamento em sua classe de célula, faça com que o método de ação do botão invoque isso.

class MyCell: UITableViewCell {
    var button: UIButton!

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

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

Então, ao criar sua célula em cellForRowAtIndexPath, você pode atribuir um valor ao seu fechamento.

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

Ao mover o código do manipulador para cá, você pode tirar vantagem do indexPathargumento já presente . Esta é uma abordagem muito mais segura do que a listada acima, pois não depende de características não documentadas.

Jacob King
fonte
2
Bem localizado. Sou um desenvolvedor competente, prometo;) - Alterei minha resposta.
Jacob King
12
Essa não é a maneira correta de obter a célula de um botão. O layout de uma célula mudou ao longo dos anos e um código como esse deixará de funcionar quando isso acontecer. Não use essa abordagem.
rmaddy
11
Esta é uma solução ruim. Ele presume detalhes sobre UITableViewCells com os quais a Apple nunca necessariamente concordou. Embora UITableViewCells tenha uma propriedade contentView, não há garantia de que a visualização de contentView sempre será a Cell.
bpapa
1
@PintuRajput Você pode me descrever sua hierarquia de visões? Provavelmente você está vendo isso porque o botão não é uma subvisão direta da visão do conteúdo da célula.
Jacob King
2
@ymutlu Concordo totalmente, afirmei isso na resposta. Também propus uma solução muito mais robusta. A razão pela qual deixei o original no lugar é porque sinto que é melhor mostrar a outros desenvolvedores os problemas com uma abordagem do que evitá-la completamente, isso não ensina nada, você vê. :)
Jacob King
61

Minha abordagem para esse tipo de problema é usar um protocolo delegado entre a célula e o tableview. Isso permite que você mantenha o manipulador de botão na subclasse de célula, o que permite atribuir o manipulador de ação de retoque à célula de protótipo no Interface Builder, enquanto mantém a lógica do manipulador de botão no controlador de visualização.

Também evita a abordagem potencialmente frágil de navegar na hierarquia de visualização ou o uso da tagpropriedade, que tem problemas quando os índices das células mudam (como resultado de inserção, exclusão ou reordenação)

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
fonte
buttonTappedé a função delegada e está no controlador de visualização. No meu exemplo, someButtonTappedé o método de ação na célula
Paulw11
@ paulw11 Não tenho nenhum botão de membro no celular. Utilizado neste método@IBAction func someButtonTapped(sender: UIButton) { self.delegate?.buttonTapped(self) }
EI Captain v2.0
1
Esta é uma solução muito boa (nem de longe tão ruim quanto as duas atualmente com mais votos, usando a tag olhando para o superview), mas parece que há muito código extra a ser adicionado.
bpapa
2
Esta é a solução correta e deve ser a resposta aceita. Ele não abusa da propriedade tag, não assume a construção de células (que podem ser facilmente alteradas pela Apple) e ainda funcionará (sem codificação extra) quando as células são movidas ou novas células adicionadas entre as células existentes.
Robotic Cat
1
@ Paulw11 Inicialmente, pensei que fosse muito código, mas ele provou ser muito mais resistente do que o que eu estava usando antes. Obrigado por postar esta solução robusta.
Adrian de
53

ATUALIZAÇÃO : Obtendo o indexPath da célula que contém o botão (seção e linha):

Usando a posição do botão

Dentro do seu buttonTappedmétodo, você pode pegar a posição do botão, convertê-lo em uma coordenada em tableView e, em seguida, obter o indexPath da linha nessa coordenada.

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

NOTA : Às vezes, você pode chegar a um caso extremo ao usar os view.convert(CGPointZero, to:self.tableView)resultados da função nilpara encontrar uma linha em um ponto, mesmo que haja uma célula tableView lá. Para corrigir isso, tente passar uma coordenada real ligeiramente deslocada da origem, como:

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

Resposta anterior: Usando a propriedade da tag (retorna apenas linha)

Em vez de subir nas árvores de supervisualização para pegar um ponteiro para a célula que contém o UIButton, há uma técnica mais segura e mais repetível que utiliza a propriedade button.tag mencionada por Antonio acima, descrita nesta resposta e mostrada abaixo:

No cellForRowAtIndexPath: você define a propriedade da tag:

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

Então, no buttonClicked: função, você faz referência a essa tag para capturar a linha do indexPath onde o botão está localizado:

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

Eu prefiro esse método, pois descobri que balançar nas árvores do superview pode ser uma maneira arriscada de projetar um aplicativo. Além disso, para o objetivo-C, usei essa técnica no passado e estou feliz com o resultado.

Iron John Bonney
fonte
5
Esta é uma boa maneira de fazer isso e irei votar a favor para que seu representante comece um pouco, no entanto, a única falha é que ele não dá acesso a indexPath.section se isso também for necessário. Ótima resposta!
Jacob King de
Obrigado Jacob! Agradeço o carma representante. Se você quisesse obter o indexPath.sectionalém do indexPath.row(sem redefinir a propriedade do tag como indexPath.section), em cellForRowAtIndexPath:você poderia apenas alterar o tag para button.tag = indexPathe, em seguida, na buttonClicked:função você poderia acessar usando sender.tag.rowe sender.tag.section.
Iron John Bonney
1
É um novo recurso, porque tenho certeza de que me lembro da propriedade do tag ser do tipo Int e não do tipo AnyObject, a menos que isso tenha mudado no swift 2.3?
Jacob King
@JacobKing você está certo! Meu mal, eu me perdi totalmente ao escrever esse comentário e estava pensando que a tag era do tipo AnyObject. Derp - não ligue para mim. Seria útil se você pudesse passar o indexPath como uma tag ...
Iron John Bonney
3
Também não é uma boa abordagem. Por um lado, ele só funcionaria em visualizações de tabela, onde há uma única seção.
bpapa
16

Use uma extensão para UITableView para buscar a célula para qualquer visualização:


A resposta de @Paulw11 de configurar um tipo de célula customizado com uma propriedade delegate que envia mensagens para a table view é um bom caminho a seguir, mas requer uma certa quantidade de trabalho para configurar.

Acho que percorrer a hierarquia de visualização da célula da table view procurando pela célula é uma má ideia. É frágil - se mais tarde você colocar o botão em uma visualização para fins de layout, o código provavelmente será quebrado.

O uso de tags de visualização também é frágil. Você deve se lembrar de configurar as tags ao criar a célula e, se usar essa abordagem em um controlador de visualização que usa tags de visualização para outro propósito, você pode ter números de tag duplicados e seu código pode não funcionar como esperado.

Eu criei uma extensão para UITableView que permite obter o indexPath para qualquer exibição contida em uma célula de exibição de tabela. Ele retorna um Optionalque será nulo se a visão passada realmente não se enquadrar em uma célula de visão de tabela. Abaixo está o arquivo fonte da extensão em sua totalidade. Você pode simplesmente colocar esse arquivo em seu projeto e, em seguida, usar o indexPathForView(_:)método incluído para encontrar o indexPath que contém qualquer visualização.

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

Para usá-lo, você pode simplesmente chamar o método na IBAction para um botão que está contido em uma célula:

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

(Observe que o indexPathForView(_:) função só funcionará se o objeto de visualização que é passado estiver contido em uma célula que está atualmente na tela. Isso é razoável, uma vez que uma visualização que não está na tela não pertence realmente a um indexPath específico; é provável que ser atribuído a um indexPath diferente quando ele contém a célula reciclada.)

EDITAR:

Você pode baixar um projeto de demonstração funcional que usa a extensão acima no Github: TableViewExtension.git

Duncan C
fonte
Obrigado, usei a extensão para obter o indexPath de um textview em uma célula - funcionou perfeitamente.
Jeremy Andrews
9

Para Swift2.1

Eu encontrei uma maneira de fazer isso, espero que ajude.

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

    guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
        fatalError("can't find point in tableView")
    }
vito especial
fonte
O que significa se o erro for executado? Quais são alguns dos motivos pelos quais ele não consegue encontrar o ponto em tableView?
OOProg
Esta (ou similar, usando os métodos de conversão UIView) deve ser a resposta aceita. Não sei por que é atualmente o nº 4, já que não faz suposições sobre a hierarquia privada de uma visualização de tabela, não usa a propriedade tag (quase sempre uma má ideia) e não envolve muito código extra.
bpapa
9

Solução Swift 4:

Você tem um botão (myButton) ou qualquer outra visualização na célula. Atribuir tag em cellForRowAt assim

cell.myButton.tag = indexPath.row

Agora em você toque em Função ou em qualquer outro. Pegue-o assim e salve-o em uma variável local.

currentCellNumber = (sender.view?.tag)!

Depois disso, você pode usar em qualquer lugar este currentCellNumber para obter o indexPath.row do botão selecionado.

Aproveitar!

Sajid Zeb
fonte
Essa abordagem funciona, mas as tags de visualização são frágeis, como mencionei na minha resposta. Uma tag de número inteiro simples não funcionará para uma exibição de tabela seccionada, por exemplo. (um IndexPath tem 2 inteiros.) Minha abordagem sempre funcionará, e não há necessidade de instalar uma tag no botão (ou outra visualização tocável.)
Duncan C
6

No Swift 4, basta usar isto:

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

        if let indexPath = tableView.indexPathForRow(at: buttonPostion) {
            let rowIndex =  indexPath.row
        }
}
DEEPAK KUMAR
fonte
A resposta mais limpa deve ser a selecionada. A única coisa a notar é que tableViewé uma variável de saída que precisa ser referenciada antes de esta resposta funcionar.
10000RubyPools
Trabalho como charme !!
Parthpatel1105
4

Muito simples obter o caminho do índice swift 4, 5

 let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
  cell.btn.tag = indexPath.row


  cell.btn.addTarget(self, action: "buttonTapped:", forControlEvents: 
UIControlEvents.TouchUpInside)

Como obter IndexPath Inside Btn Click:

    func buttonTapped(_ sender: UIButton) {`
          print(sender.tag) .  


}
M Murteza
fonte
3

Como o remetente do manipulador de eventos é o próprio botão, eu usaria a tagpropriedade do botão para armazenar o índice inicializado em cellForRowAtIndexPath.

Mas com um pouco mais de trabalho eu faria de uma maneira completamente diferente. Se você estiver usando uma célula personalizada, é como eu abordaria o problema:

  • adicione uma propriedade 'indexPath` à célula da tabela personalizada
  • inicialize-o em cellForRowAtIndexPath
  • mover o manipulador de toque do controlador de visualização para a implementação da célula
  • use o padrão de delegação para notificar o controlador de visualização sobre o evento tap, passando o caminho do índice
Antonio
fonte
Antonio, tenho um celular personalizado e adoraria fazer do seu jeito. No entanto, não está funcionando. Quero que meu código 'deslize para revelar o botão de exclusão' seja executado, que é o método tableView commitEditingStyle. Removi esse código da classe mainVC e coloquei-o na classe customCell, mas agora o código não funciona mais. o que estou perdendo?
Dave G de
Eu acho que essa é a melhor maneira de obter o indexPath de uma célula com x seções, no entanto, não vejo a necessidade dos marcadores 3 e 4 em uma abordagem MVC
Edward
2

Depois de ver a sugestão de Paulw11 de usar um retorno de chamada de delegado, eu queria elaborá-la um pouco / apresentar outra sugestão semelhante. Se você não quiser usar o padrão de delegado, pode utilizar os fechamentos rapidamente da seguinte forma:

Sua classe de celular:

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

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

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

Seu cellForRowAtIndexPathmétodo:

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.
}
Jacob King
fonte
2

Eu descobri uma maneira muito fácil de usar para gerenciar qualquer célula em tableView e collectionView usando uma classe Model e isso funciona perfeitamente.

Na verdade, há uma maneira muito melhor de lidar com isso agora. Isso funcionará para gerenciar célula e valor.

Aqui está minha saída (captura de tela), então veja isto:

insira a descrição da imagem aqui

  1. É muito simples criar uma classe de modelo , siga o procedimento abaixo. Crie uma classe rápida com o nome RNCheckedModel, escreva o código como abaixo.
class RNCheckedModel: NSObject {

    var is_check = false
    var user_name = ""

    }
  1. crie sua classe de celular
class InviteCell: UITableViewCell {

    @IBOutlet var imgProfileImage: UIImageView!
    @IBOutlet var btnCheck: UIButton!
    @IBOutlet var lblName: UILabel!
    @IBOutlet var lblEmail: UILabel!
    }
  1. e, finalmente, use a classe de modelo em seu UIViewController ao usar seu 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()

    }

     }
Paresh Mangukiya
fonte
1

Usei o método convertPoint para obter o ponto de tableview e passar este ponto para o método indexPathForRowAtPoint para obter 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);
            }   
        }
    }
Avijit Nagare
fonte
1

Tente usar #selector para chamar o IBaction.In the cellforrowatindexpath

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

Desta forma você pode acessar o indexpath dentro do método editButtonPressed

func editButtonPressed(_ sender: UIButton) {

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

}
Vineeth Krishnan
fonte
Resposta mais apropriada
Amalendu Kar
Não, as tags serão desativadas quando o usuário adicionar ou remover células.
Koen
1

No meu caso, eu tenho várias seções e o índice da seção e da linha é vital, então, nesse caso, acabei de criar uma propriedade no UIButton que defini a célula indexPath assim:

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

Em seguida, defina a propriedade em cellForRowAt assim:

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

Em seguida, em handleTapAction você pode obter o indexPath assim:

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

}
DvixExtract
fonte
1

Swift 4 e 5

Método 1 usando protocolo delegado

Por exemplo, você tem um UITableViewCellcom nomeMyCell

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

Agora crie um protocol

protocol MyCellDelegate {
    func didPressButton(cell: UITableViewCell)
}

Próxima etapa, crie uma extensão de UITableView

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

Em sua UIViewControllerimplementação do protocoloMyCellDelegate

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

Método 2 usando fechamentos

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

Nota: - se você deseja obter UICollectionViewindexPath, pode usá-lo UICollectionView extensione repetir as etapas acima

extension UICollectionView {
    func returnIndexPath(cell: UICollectionViewCell) -> IndexPath?{
        guard let indexPath = self.indexPath(for: cell) else {
            return nil
        }
        return indexPath
    }
}
Rashid Latif
fonte
0

Em Swift 3. Também usado instruções de guarda, evitando uma longa cadeia de chaves.

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

    print(indexPath)
}
Sean
fonte
Isso não vai funcionar. A supervisão do botão não será a célula.
rmaddy
Isso funciona. A única coisa que você deve observar é que a pilha de visualização de cada pessoa é diferente. Pode ser sender.superview, sender.superview.superview ou sender.superview.superview.superview. Mas funciona muito bem.
Sean
0

Às vezes, o botão pode estar dentro de outra visualização de UITableViewCell. Nesse caso, superview.superview pode não fornecer o objeto de célula e, portanto, o indexPath será nulo.

Nesse caso, devemos continuar encontrando a supervisão até obter o objeto de célula.

Função para obter objeto de célula por superview

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
}

Agora podemos obter indexPath no toque do botão como abaixo

@IBAction func tapButton(_ sender: UIButton)
{
    let cell = getCellForView(view: sender)
    let indexPath = myTabelView.indexPath(for: cell)
}
Teena Nath Paul
fonte
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
fonte