swift - rápido: ¿cómo obtener indexpath.row cuando se toca un botón en una celda?
button touch (14)
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 usted toque Función o cualquier otro. Recógelo así y guárdalo 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!
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)
}
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 delegada que envía mensajes a la vista de tabla es una buena manera de hacerlo, pero requiere una cierta cantidad de trabajo para configurarlo.
Creo que recorrer la jerarquía de vistas de la celda de vista de tabla buscando la celda es una mala idea. Es frágil: si luego encierra su botón en una vista para fines de diseño, es probable que ese código se rompa.
Usar etiquetas de vista también es frágil. Debe recordar configurar las etiquetas cuando cree 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 espera.
He creado una extensión para UITableView que le permite obtener indexPath para cualquier vista contenida en una celda de vista de tabla.
Devuelve un
Optional
que será nulo si la vista pasada en realidad no cae dentro de una celda de vista de tabla.
A continuación se muestra el archivo fuente de extensión en su totalidad.
Simplemente puede poner este archivo en su proyecto y luego usar el método
indexPathForView(_:)
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 función
indexPathForView(_:)
solo funcionará si el objeto de vista que se pasa está contenido en una celda que está actualmente en pantalla. Eso es razonable, ya que una vista que no está en pantalla no pertenece realmente a un objeto específico indexPath; es probable que se asigne a un indexPath diferente cuando contiene una celda que se recicla).
EDITAR:
Puede descargar un proyecto de demostración en funcionamiento que utiliza la extensión anterior de Github: TableViewExtension.git
A veces, el botón puede estar dentro de otra vista de UITableViewCell. En ese caso, superview.superview puede no proporcionar el objeto de celda y, por lo tanto, indexPath será nulo.
En ese caso, debemos seguir buscando la supervista hasta que obtengamos el objeto de celda.
Función para obtener objeto de 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)
}
Dado que el remitente del controlador de eventos es el botón en sí, usaría la propiedad de
tag
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:
- agregar una propiedad ''indexPath` a la celda de la tabla personalizada
-
Inicializarlo en
cellForRowAtIndexPath
- mover el controlador 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 de tap, pasando la ruta del índice
Después de ver la sugerencia de Paulw11 de usar una devolución de llamada de delegado, quise elaborarlo un poco / presentar otra sugerencia similar. Si no desea utilizar el patrón de delegado, puede utilizar cierres 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)
}
}
Su método
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.
}
En Swift 3. También se usaron 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)
}
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
}
}
Intente usar #selector para llamar a IBaction. En el cellforrowatindexpath
cell.editButton.tag = indexPath.row
cell.editButton.addTarget(self, action: #selector(editButtonPressed), for: .touchUpInside)
De esta forma puede acceder a la ruta de acceso indexada dentro del método editButtonPressed
func editButtonPressed(_ sender: UIButton) {
print(sender.tag)//this value will be same as indexpath.row
}
Mi enfoque para este tipo de problema es usar un protocolo delegado entre la celda y la vista de tabla. Esto le permite mantener el controlador de botones 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 botones 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 propiedad de
tag
, que tiene problemas cuando cambian los índices de las celdas (como resultado de la inserción, eliminación o reordenamiento)
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)")
}
}
Para
Swift2.1
Encontré una manera de hacerlo, con suerte, me ayudará.
let point = tableView.convertPoint(CGPoint.zero, fromView: sender)
guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
fatalError("can''t find point in tableView")
}
Usé el método convertPoint para obtener un punto de tableview 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);
}
}
}
giorashc casi lo tuvo con su respuesta, pero pasó por alto el hecho de que las celdas tienen una capa
contentView
adicional.
Por lo tanto, tenemos que ir una capa 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 vista de tabla tiene celdas como subvistas que posteriormente tienen sus propias ''vistas de contenido'', por eso 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 estar directamente en la vista de contenido de la celda, tendrá que ir más allá de las capas para acceder.
Lo anterior es uno de esos enfoques, pero no necesariamente el mejor enfoque.
Si bien es funcional, asume detalles sobre un
UITableViewCell
que Apple nunca ha documentado necesariamente, como su jerarquía de vistas.
Esto podría cambiarse en el futuro y, como resultado, el código anterior puede 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 que lea, 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 su código de controlador aquí, puede aprovechar el argumento
indexPath
ya presente.
Este es un enfoque mucho más seguro que el mencionado anteriormente, ya que no se basa en rasgos indocumentados.
ACTUALIZACIÓN : Obteniendo el indexPath de la celda que contiene el botón (tanto la sección como la fila):
Usando la posición del botón
Dentro de su método
buttonTapped
, puede tomar la posición del botón, convertirlo en una coordenada en la 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 de borde cuando usa la función
view.convert(CGPointZero, to:self.tableView)
resulta en encontrar
nil
para una fila en un punto, aunque haya una celda tableView allí.
Para solucionar esto, intente pasar una coordenada real que esté ligeramente desplazada del origen, como:
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 fila)
En lugar de trepar a los árboles de la vista superior para agarrar un puntero a la celda que contiene el UIButton, existe una técnica más segura y más repetible que utiliza la propiedad button.tag mencionada anteriormente por Antonio, descrita en esta respuesta , y que se muestra a continuación:
En
cellForRowAtIndexPath:
establece la propiedad de etiqueta:
button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
Luego, en la función
buttonClicked:
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 la 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.
Encontré una forma muy fácil y eficiente 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 (screenshote) así que mira esto
aqui esta mi codigo
- Es muy sencillo crear clas de modelo , siga el procedimiento a continuación. Cree una clase rápida con el nombre "RNCheckedModel", escriba el código de la siguiente manera.
clase RNCheckedModel: NSObject {
var is_check = false
var user_name = ""
}
- crea tu clase celular
clase InviteCell: UITableViewCell {
@IBOutlet var imgProfileImage: UIImageView!
@IBOutlet var btnCheck: UIButton!
@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
}
- y finalmente use la clase de modelo en su UIViewController cuando use su UITableView .
clase 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()
}
}