¿Cómo obtener indexpath.row cuando un elemento está activado?


104

Tengo una vista de tabla con botones y quiero usar indexpath.row cuando se toca uno de ellos. Esto es lo que tengo actualmente, pero siempre es 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)
}

¿Debo usar IndexPathForSelectedRow () en lugar de la variable de punto? o donde debo usarlo?
Vincent

Respuestas:


164

giorashc casi lo tiene con su respuesta, pero pasó por alto el hecho de que las celdas tienen una contentViewcapa extra . Por lo tanto, tenemos que ir un nivel más profundo:

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

let indexPath = itemTable.indexPath(for: cell)

Esto se debe a que dentro de la jerarquía de vistas, una tableView tiene celdas como subvistas que posteriormente tienen sus propias 'vistas de contenido', por lo que debe obtener la supervista de esta vista de contenido para obtener la celda en sí. Como resultado de esto, si su botón está contenido en una subvista en lugar de directamente en la vista de contenido de la celda, tendrá que profundizar muchas capas para acceder a ella.

El anterior es uno de esos enfoques, pero no necesariamente el mejor. Si bien es funcional, asume detalles sobre un UITableViewCellque Apple nunca necesariamente ha documentado, como su jerarquía de vista. Esto podría cambiarse en el futuro y, como resultado, el código anterior podría comportarse de manera impredecible.

Como resultado de lo anterior, por razones de longevidad y confiabilidad, recomiendo adoptar otro enfoque. Hay muchas alternativas enumeradas en este hilo, y le animo a leer, pero mi favorito personal es el siguiente:

Mantenga una propiedad de un cierre en su clase de celda, haga que el método de acción del botón invoque esto.

class MyCell: UITableViewCell {
    var button: UIButton!

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

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

Luego, cuando crea su celda en cellForRowAtIndexPath, puede asignar un valor a su cierre.

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

Al mover el código de su controlador aquí, puede aprovechar el indexPathargumento ya presente . Este es un enfoque mucho más seguro que el mencionado anteriormente, ya que no se basa en rasgos indocumentados.


2
Bien descrito. Soy un desarrollador competente, lo prometo;) - He modificado mi respuesta.
Jacob King

12
Esta no es la forma correcta de obtener la celda con un botón. El diseño de una celda ha cambiado a lo largo de los años y un código como este no funcionará cuando eso suceda. No utilice este enfoque.
rmaddy

11
Ésta es una mala solución. Asume detalles sobre UITableViewCells que Apple nunca ha aceptado necesariamente. Si bien UITableViewCells tiene una propiedad contentView, no hay garantía de que la supervista de contentView siempre sea la celda.
bpapa

1
@PintuRajput ¿Puede describirme su jerarquía de vistas, por favor? Es probable que vea esto porque su botón no es una subvista directa de la vista de contenido de la celda.
Jacob King

2
@ymutlu Estoy totalmente de acuerdo, lo dije en la respuesta. También propuse una solución mucho más robusta. La razón por la que dejé el original en su lugar es porque creo que es mejor mostrarles a otros desarrolladores los problemas con un enfoque que esquivarlos por completo, esto les enseña nada de lo que ves. :)
Jacob King

61

Mi enfoque para este tipo de problema es utilizar un protocolo delegado entre la celda y la vista de tabla. Esto le permite mantener el controlador de botón en la subclase de celda, lo que le permite asignar el controlador de acción de retoque a la celda prototipo en Interface Builder, mientras mantiene la lógica del controlador de botón en el controlador de vista.

También evita el enfoque potencialmente frágil de navegar por la jerarquía de vistas o el uso de la tagpropiedad, que tiene problemas cuando los índices de las celdas cambian (como resultado de la inserción, eliminación o reordenación)

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

buttonTappedes la función delegada y está en el controlador de vista. En mi ejemplo, ¿ someButtonTappedes el método de acción en la celda
Paulw11

@ paulw11 Tengo celda no tiene botón de miembro Tocado en este método@IBAction func someButtonTapped(sender: UIButton) { self.delegate?.buttonTapped(self) }
EI Captain v2.0

1
Esta es una solución bastante buena (ni mucho menos tan mala como las dos actualmente con más votos, usando la etiqueta que mira la supervista) pero parece que hay demasiado código adicional para agregar.
bpapa

2
Esta es la solución correcta y debería ser la respuesta aceptada. No abusa de la propiedad de la etiqueta, no asume la construcción de celdas (que Apple puede cambiar fácilmente) y seguirá funcionando (sin codificación adicional) cuando las celdas se muevan o se agreguen nuevas celdas entre las celdas existentes.
Gato robótico

1
@ Paulw11 Al principio pensé que se trataba de mucho código, pero ha demostrado ser mucho más resistente que lo que estaba usando antes. Gracias por publicar esta sólida solución.
Adrian

53

ACTUALIZACIÓN : Obteniendo el indexPath de la celda que contiene el botón (sección y fila):

Uso de la posición del botón

Dentro de su buttonTappedmétodo, puede tomar la posición del botón, convertirlo en una coordenada en el tableView, luego obtener el indexPath de la fila en esa coordenada.

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

NOTA : A veces puede encontrarse con un caso límite cuando el uso de la función view.convert(CGPointZero, to:self.tableView)da como resultado la búsqueda nilde una fila en un punto, aunque haya una celda tableView allí. Para solucionar este problema, intente pasar una coordenada real que esté ligeramente desplazada del origen, como por ejemplo:

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

Respuesta anterior: uso de la propiedad de etiqueta (solo devuelve la fila)

En lugar de trepar a los árboles de supervista para agarrar un puntero a la celda que contiene el UIButton, existe una técnica más segura y repetible que utiliza la propiedad button.tag mencionada por Antonio anteriormente, descrita en esta respuesta y que se muestra a continuación:

En cellForRowAtIndexPath:usted establece la propiedad de la etiqueta:

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

Luego, en la buttonClicked:función, hace referencia a esa etiqueta para tomar la fila de indexPath donde se encuentra el botón:

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

Prefiero este método ya que descubrí que balancearse en los árboles de supervista puede ser una forma arriesgada de diseñar una aplicación. Además, para el objetivo-C he usado esta técnica en el pasado y estoy contento con el resultado.


5
Esta es una buena forma de hacerlo, y la votaré para que su representante comience un poco, sin embargo, el único defecto es que no da acceso a indexPath.section si esto también es necesario. ¡Sin embargo, gran respuesta!
Jacob King

¡Gracias Jacob! Agradezco el karma de representación. Si desea obtener el indexPath.sectionademás del indexPath.row(sin restablecer la propiedad de la etiqueta como indexPath.section), cellForRowAtIndexPath:puede simplemente cambiar la etiqueta a button.tag = indexPath, y luego en la buttonClicked:función puede acceder a ambos usando sender.tag.rowy sender.tag.section.
Iron John Bonney

1
¿Es esta una característica nueva, porque estoy seguro de que recuerdo que la propiedad de la etiqueta es de tipo Int y no AnyObject, a menos que eso haya cambiado en swift 2.3?
Jacob King

@JacobKing, ¡tienes razón! Mi mal fue totalmente espaciado al escribir ese comentario y estaba pensando que la etiqueta era de tipo AnyObject. Derp, no me hagas caso. Sin embargo, sería útil si pudiera pasar indexPath como una etiqueta ...
Iron John Bonney

3
Tampoco es un buen enfoque. Por un lado, solo funcionaría en Vistas de tabla donde hay una sola sección.
bpapa

16

Use una extensión de UITableView para obtener la celda para cualquier vista:


La respuesta de @ Paulw11 de configurar un tipo de celda personalizado con una propiedad de delegado que envía mensajes a la vista de tabla es un buen camino a seguir, pero requiere una cierta cantidad de trabajo para configurar.

Creo que recorrer la jerarquía de vistas de la celda de la vista de tabla en busca de la celda es una mala idea. Es frágil: si luego incluye su botón en una vista con fines de diseño, es probable que ese código se rompa.

El uso de etiquetas de vista también es frágil. Debe recordar configurar las etiquetas cuando crea la celda, y si usa ese enfoque en un controlador de vista que usa etiquetas de vista para otro propósito, puede tener números de etiqueta duplicados y su código puede no funcionar como se esperaba.

He creado una extensión para UITableView que le permite obtener el indexPath para cualquier vista que esté contenida en una celda de vista de tabla. Devuelve un valor Optionalque será nulo si la vista pasada en realidad no se encuentra dentro de una celda de vista de tabla. A continuación se muestra el archivo de origen de la extensión en su totalidad. Simplemente puede poner este archivo en su proyecto y luego usar el indexPathForView(_:)método incluido para encontrar el indexPath que contiene cualquier vista.

//
//  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 usarlo, simplemente puede llamar al método en IBAction para un botón que está contenido en una celda:

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

(Tenga en cuenta que la indexPathForView(_:)función solo funcionará si el objeto de vista que se le pasa está contenido en una celda que se encuentra actualmente en pantalla. Eso es razonable, ya que una vista que no está en pantalla no pertenece a una ruta de índice específica; es probable que asignarse a un indexPath diferente cuando se recicla la celda que contiene).

EDITAR:

Puede descargar un proyecto de demostración funcional que utiliza la extensión anterior de Github: TableViewExtension.git


Gracias, utilicé la extensión para obtener el indexPath de una vista de texto en una celda, funcionó perfectamente.
Jeremy Andrews

9

por Swift2.1

Encontré una manera de hacerlo, con suerte, ayudará.

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

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

¿Qué significa si se ejecuta el error? ¿Cuáles son algunas de las razones por las que no puede encontrar el punto en tableView?
OOProg

Esta (o similar, utilizando los métodos de conversión de UIView) debería ser la respuesta aceptada. No estoy seguro de por qué es actualmente el número 4, ya que no hace suposiciones sobre la jerarquía privada de una vista de tabla, no usa la propiedad de etiqueta (casi siempre es una mala idea) y no implica mucho código adicional.
bpapa

9

Solución Swift 4:

Tiene un botón (myButton) o cualquier otra vista en la celda. Asignar etiqueta en cellForRowAt así

cell.myButton.tag = indexPath.row

Ahora en tapFunction o cualquier otro. Consígalo así y guárdelo en una variable local.

currentCellNumber = (sender.view?.tag)!

Después de esto, puede usar en cualquier lugar este currentCellNumber para obtener el indexPath.row del botón seleccionado.

¡Disfrutar!


Ese enfoque funciona, pero las etiquetas de vista son frágiles, como se menciona en mi respuesta. Una etiqueta de entero simple no funcionará para una vista de tabla seccionada, por ejemplo. (un IndexPath tiene 2 enteros). Mi enfoque siempre funcionará, y no es necesario instalar una etiqueta en el botón (u otra vista que se pueda tocar).
Duncan C

6

En Swift 4, solo usa esto:

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

        if let indexPath = tableView.indexPathForRow(at: buttonPostion) {
            let rowIndex =  indexPath.row
        }
}

La respuesta más limpia, debe ser la seleccionada. Lo único a tener en cuenta es que tableViewes una variable de salida a la que se debe hacer referencia antes de que esta respuesta funcione.
10000RubyPools

¡Trabaja como el encanto!
Parthpatel1105

4

Muy simple conseguir Index Path rápido 4, 5

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


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

Cómo obtener IndexPath Inside Btn Click:

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


}

3

Dado que el remitente del controlador de eventos es el botón en sí, usaría la tagpropiedad del botón para almacenar el índice, inicializado en cellForRowAtIndexPath.

Pero con un poco más de trabajo lo haría de una manera completamente diferente. Si está utilizando una celda personalizada, así es como abordaría el problema:

  • agregue una propiedad 'indexPath` a la celda de la tabla personalizada
  • inicializarlo en cellForRowAtIndexPath
  • mover el manejador de tap del controlador de vista a la implementación de la celda
  • use el patrón de delegación para notificar al controlador de vista sobre el evento tap, pasando la ruta del índice

Antonio, tengo un celular personalizado y me encantaría hacerlo a tu manera. Sin embargo, no funciona. Quiero que se ejecute mi código de 'deslizar para revelar el botón de eliminación', que es el método tableView commitEditingStyle. Eliminé ese código de la clase mainVC y lo puse en la clase customCell, pero ahora el código ya no funciona. ¿Qué me estoy perdiendo?
Dave G

Creo que esta es la mejor manera de obtener el indexPath de una celda con x secciones, sin embargo, no veo la necesidad de viñetas 3 y 4 en un enfoque MVC
Edward

2

Después de ver la sugerencia de Paulw11 de usar una devolución de llamada de delegado, quise desarrollarla un poco / presentar otra sugerencia similar. Si no desea utilizar el patrón de delegado, puede utilizar cierres rápidos de la siguiente manera:

Tu clase celular:

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

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

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

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

2

Encontré una forma muy fácil de usar para administrar cualquier celda en tableView y collectionView usando una clase Model y esto funciona perfectamente.

De hecho, hay una manera mucho mejor de manejar esto ahora. Esto funcionará para administrar la celda y el valor.

Aquí está mi salida (captura de pantalla), así que mira esto:

ingrese la descripción de la imagen aquí

  1. Es muy simple crear una clase modelo , siga el procedimiento a continuación. Cree una clase rápida con nombre RNCheckedModel, escriba el código como se muestra a continuación.
class RNCheckedModel: NSObject {

    var is_check = false
    var user_name = ""

    }
  1. crea tu clase celular
class InviteCell: UITableViewCell {

    @IBOutlet var imgProfileImage: UIImageView!
    @IBOutlet var btnCheck: UIButton!
    @IBOutlet var lblName: UILabel!
    @IBOutlet var lblEmail: UILabel!
    }
  1. y finalmente use la clase modelo en su UIViewController cuando use su 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

Utilicé el método convertPoint para obtener el punto de la vista de tabla y pasar este punto al método indexPathForRowAtPoint para obtener 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

Intente usar #selector para llamar a la IBaction.In the cellforrowatindexpath

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

De esta manera puede acceder a la ruta de índice dentro del método editButtonPressed

func editButtonPressed(_ sender: UIButton) {

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

}

Respuesta más apropiada
Amalendu Kar

No, las etiquetas estarán desactivadas cuando el usuario agregue o elimine celdas.
koen

1

En mi caso, tengo varias secciones y tanto la sección como el índice de fila son vitales, por lo que, en tal caso, acabo de crear una propiedad en UIButton que configuré la celda indexPath así:

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

Luego establezca la propiedad en cellForRowAt así:

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

Luego, en handleTapAction puede obtener el indexPath así:

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

}

1

Swift 4 y 5

Método 1 usando el delegado de protocolo

Por ejemplo, tienes un UITableViewCellnombre conMyCell

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

Ahora crea un protocol

protocol MyCellDelegate {
    func didPressButton(cell: UITableViewCell)
}

Siguiente paso, cree una extensión de UITableView

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

En su UIViewControllerimplementación el protocoloMyCellDelegate

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

Método 2 usando cierres

En 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: - si desea obtener UICollectionViewindexPath, puede usar esto UICollectionView extensiony repetir los pasos anteriores

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

0

En Swift 3. También se utilizan declaraciones de guardia, evitando una larga cadena de llaves.

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

    print(indexPath)
}

Esto no funcionará. La supervista del botón no será la celda.
rmaddy

Esto funciona. Lo único que debe tener en cuenta es que la pila de vistas de todos es diferente. Podría ser sender.superview, sender.superview.superview o sender.superview.superview.superview. Pero funciona muy bien.
Sean

0

A veces, el botón puede estar dentro de otra vista de UITableViewCell. En ese caso, es posible que superview.superview no proporcione el objeto de celda y, por lo tanto, indexPath será nulo.

En ese caso, deberíamos seguir buscando la supervista hasta que obtengamos el objeto de celda.

Función para obtener el objeto de la celda por supervista

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
}

Ahora podemos obtener indexPath al tocar el botón como se muestra a continuación

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

}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.