Haga clic en el botón dentro de UITableViewCell

140

Tengo un controlador de vista con una vista de tabla y una punta separada para la plantilla de celda de tabla. La plantilla de celda tiene algunos botones. Quiero acceder al clic del botón junto con el índice de la celda en la que se hizo clic dentro del controlador de vista donde he definido la vista de tabla.

Entonces tengo ViewController.hy ViewController.mdonde tengo el UITableViewy TableTemplate.h, TableTemplate.my TableTemplate.xibdonde tengo definida la punta. Quiero el evento de clic de botón con índice de celda en ViewController.m.

¿Alguna ayuda sobre cómo puedo hacer eso?

ankit_rck
fuente

Respuestas:

258

1) En su cellForRowAtIndexPath:método, asigne la etiqueta del botón como índice:

cell.yourbutton.tag = indexPath.row;

2) Agregue objetivo y acción para su botón de la siguiente manera:

[cell.yourbutton addTarget:self action:@selector(yourButtonClicked:) forControlEvents:UIControlEventTouchUpInside];

3) Acciones de código basadas en el índice como se muestra a continuación en ViewControler:

-(void)yourButtonClicked:(UIButton*)sender
{
     if (sender.tag == 0) 
     {
         // Your code here
     }
}

Actualizaciones para múltiples secciones:

Puede consultar este enlace para detectar el clic del botón en la vista de tabla para varias filas y secciones.

Mani
fuente
1
Esto también se puede hacer a través de Interface Builder (IB) en el paso dos. Solo asegúrese de que su etiqueta de botones esté configurada. Realmente no quieres mezclar tu llamado a la acción. Hazlo a través de IB o explícitamente en tu código.
Sententia
@Mani No rompe MVC: la acción está en TableView, no en la celda.
davecom
@davecom Si configura el objetivo del botón como celda (a través de IB), ¿cómo se activará desde tableView? ¿O es su forma de conectar el objetivo del botón a la vista de tabla que se coloca en el xib de la celda?
Mani
24
Esta solución tiene problemas cuando comienza a insertar y eliminar filas. La etiqueta no se actualiza cuando las filas se desplazan. En lugar de mantener una referencia a la fila. Puede ser mejor mantener una referencia a un ID de objeto único.
Vincent Cheong
1
Cada vez que te encuentras asignando valores a los atributos de etiqueta de las vistas, tienes un olor a código muy malo que puede morderte más adelante. Busque mejores formas de lograr su objetivo, no la primera publicación SO que encuentre.
TigerCoding
148

Los delegados son el camino a seguir.

Como se ve con otras respuestas, el uso de vistas puede quedar desactualizado. Quién sabe mañana, podría haber otro contenedor y tal vez deba usarlo cell superview]superview]superview]superview]. Y si usa etiquetas, terminaría con n número de condiciones si no para identificar la celda. Para evitar todo eso, configure delegados. (Al hacerlo, creará una clase de celda reutilizable. Puede usar la misma clase de celda que una clase base y todo lo que tiene que hacer es implementar los métodos de delegado).

Primero necesitamos una interfaz (protocolo) que será utilizada por la célula para comunicar (delegar) los clics de los botones. ( Puede crear un archivo .h separado para el protocolo e incluirlo tanto en el controlador de vista de tabla como en las clases de celda personalizadas O simplemente agregarlo en la clase de celda personalizada que de todos modos se incluirá en el controlador de vista de tabla )

@protocol CellDelegate <NSObject>
- (void)didClickOnCellAtIndex:(NSInteger)cellIndex withData:(id)data;
@end

Incluya este protocolo en el controlador de vista de tabla y celda personalizado. Y asegúrese de que el controlador de vista de tabla confirme este protocolo.

En la celda personalizada, cree dos propiedades:

@property (weak, nonatomic) id<CellDelegate>delegate;
@property (assign, nonatomic) NSInteger cellIndex;

En el UIButtondelegado de IBAction, haga clic en: ( Se puede hacer lo mismo para cualquier acción en la clase de celda personalizada que deba delegarse nuevamente para ver el controlador )

- (IBAction)buttonClicked:(UIButton *)sender {
    if (self.delegate && [self.delegate respondsToSelector:@selector(didClickOnCellAtIndex:withData:)]) {
        [self.delegate didClickOnCellAtIndex:_cellIndex withData:@"any other cell data/property"];
    }
}

En el controlador de vista de tabla cellForRowAtIndexPathdespués de quitar la celda, establezca las propiedades anteriores.

cell.delegate = self;
cell.cellIndex = indexPath.row; // Set indexpath if its a grouped table.

E implemente el delegado en el controlador de vista de tabla:

- (void)didClickOnCellAtIndex:(NSInteger)cellIndex withData:(id)data
{
    // Do additional actions as required.
    NSLog(@"Cell at Index: %d clicked.\n Data received : %@", cellIndex, data);
}

Este sería el enfoque ideal para obtener acciones personalizadas de botón de celda en el controlador de vista de tabla.

GoodSp33d
fuente
2
¿Por qué ha hecho del delegado una propiedad fuerte de la célula? Esto le dará un ciclo de retención, a menos que sepa que el controlador solo sostiene débilmente la celda.
JulianSymes
¿Qué pasa con el _cellIndex beign actualizado después de que se elimina la celda?
skornos
2
Un amigo me dijo que usar delegado en cada celda causa consumo de memoria, así que usa etiquetas. ¿Es esto cierto?
Bista
2
mira
@the_UB No puede haber mucho entre configurar una etiqueta y almacenar una sola referencia. Posiblemente una etiqueta tomaría más memoria.
Ian Warburton
66

En lugar de jugar con etiquetas, tomé un enfoque diferente. Delegado para mi subclase de UITableViewCell (OptionButtonsCell) y agregué una var indexPath. Desde mi botón en el guión gráfico, conecté @IBAction a OptionButtonsCell y allí envié el método delegado con el indexPath correcto a cualquier persona interesada. En la celda para la ruta del índice configuré indexPath actual y funciona :)

Deje que el código hable por sí mismo:

Swift 3 Xcode 8

OptionButtonsTableViewCell.swift

import UIKit
protocol OptionButtonsDelegate{
    func closeFriendsTapped(at index:IndexPath)
}
class OptionButtonsTableViewCell: UITableViewCell {
    var delegate:OptionButtonsDelegate!
    @IBOutlet weak var closeFriendsBtn: UIButton!
    var indexPath:IndexPath!
    @IBAction func closeFriendsAction(_ sender: UIButton) {
        self.delegate?.closeFriendsTapped(at: indexPath)
    }
}

MyTableViewController.swift

class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate {...

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "optionCell") as! OptionButtonsTableViewCell
    cell.delegate = self
    cell.indexPath = indexPath
    return cell   
}

func closeFriendsTapped(at index: IndexPath) {
     print("button tapped at index:\(index)")
}
Maciej Chrzastek
fuente
me pueden ayudar, recibo un error en esta línea: class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate // error: conformidad redundante de 'MyTableViewController' al protocolo 'UITableViewDataSource'
Ulug'bek Ro'zimboyev
Parece que está intentando ajustarse a UITableViewDataSource varias veces. ¿Quizás tiene una extensión donde ya se ajusta a la fuente de datos ?, no puede ayudar más sin código
Maciej Chrzastek
1
y cómo pasar datos para realizar segue e ir a otro controlador de vista?
Milad Faridnia
2
¡La mejor y más limpia solución!
appsunited
31

Esto debería ayudar:

UITableViewCell* cell = (UITableViewCell*)[sender superview];
NSIndexPath* indexPath = [myTableView indexPathForCell:cell];

Aquí el remitente es la instancia de UIButton que envía el evento. myTableView es la instancia de UITableView con la que está tratando.

Simplemente obtenga la referencia de celda correcta y todo el trabajo está hecho.

Es posible que deba eliminar los botones de contentView de la celda y agregarlos directamente a la instancia de UITableViewCell como subvista.

O

Puede formular un esquema de nombres de etiquetas para diferentes UIButtons en cell.contentView. Con esta etiqueta, más adelante puede conocer la información de la fila y la sección según sea necesario.

Tarun
fuente
44
debería ser [[superview remitente] superview];
pierre23
2
Esto es bueno para células muy simples. Sin embargo, si su celda tiene un árbol de visión profundo, la respuesta de Mani es la mejor.
Sententia
3
Ahora en iOS 7 debería ser UITableViewCell * cell = (UITableViewCell *) [[[remitente superview] superview] superview]; Gracias.
Rajan Maharjan
mira
22

El siguiente código puede ayudarlo.

He tomado UITableViewcon la clase de celda prototipo personalizada nombrada UITableViewCelldentro UIViewController.

Así que tengo ViewController.h, ViewController.my TableViewCell.h,TableViewCell.m

Aquí está el código para eso:

ViewController.h

@interface ViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>

@property (strong, nonatomic) IBOutlet UITableView *tblView;

@end

ViewController.m

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return (YourNumberOfRows);
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    static NSString *cellIdentifier = @"cell";

    __weak TableViewCell *cell = (TableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

    if (indexPath.row==0) {
        [cell setDidTapButtonBlock:^(id sender)
         {
             // Your code here

         }];
    }    
    return cell;
}

Clase de celda personalizada:

TableViewCell.h

@interface TableViewCell : UITableViewCell

@property (copy, nonatomic) void (^didTapButtonBlock)(id sender);

@property (strong, nonatomic) IBOutlet UILabel *lblTitle;
@property (strong, nonatomic) IBOutlet UIButton *btnAction;

- (void)setDidTapButtonBlock:(void (^)(id sender))didTapButtonBlock;

@end

y

UITableViewCell.m

@implementation TableViewCell

- (void)awakeFromNib {
    // Initialization code
    [self.btnAction addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];

}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}
- (void)didTapButton:(id)sender {
    if (self.didTapButtonBlock)
    {
        self.didTapButtonBlock(sender);
    }
}

Nota : Aquí he tomado todo UIControlsusando Storyboard.

Espero que pueda ayudarte ... !!!

Piyush
fuente
La mejor manera
Daniel Raouf
15

La razón por la que me gusta la técnica a continuación es porque también me ayuda a identificar la sección de la tabla.

Agregar botón en la celda cellForRowAtIndexPath:

 UIButton *selectTaskBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        [selectTaskBtn setFrame:CGRectMake(15, 5, 30, 30.0)];
        [selectTaskBtn setTag:indexPath.section]; //Not required but may find useful if you need only section or row (indexpath.row) as suggested by MR.Tarun 
    [selectTaskBtn addTarget:self action:@selector(addTask:)   forControlEvents:UIControlEventTouchDown];
[cell addsubview: selectTaskBtn];

Evento addTask:

-(void)addTask:(UIButton*)btn
{
    CGPoint buttonPosition = [btn convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     int currentIndex = indexPath.row;
     int tableSection = indexPath.section;
    }
}

Espera esta ayuda.

Yogesh Lolusare
fuente
mira
12

Use cierres rápidos:

class TheCell: UITableViewCell {

    var tapCallback: (() -> Void)?

    @IBAction func didTap(_ sender: Any) {
        tapCallback?()
    }
}

extension TheController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: TheCell.identifier, for: indexPath) as! TheCell {
            cell.tapCallback = {
                //do stuff
            }
            return cell
    }
}
valexa
fuente
7

El código de Tarun no funciona en iOS7, ya que la estructura UITableViewCell cambió y ahora obtendría "UITableViewCellScrollView" en su lugar.

Esta publicación Getting UITableViewCell con superview en iOS 7 tiene una buena solución para crear un bucle para encontrar la vista principal correcta, independientemente de cualquier cambio futuro en la estructura. Se reduce a crear un bucle:

    UIView *superView = [sender superview];
    UIView *foundSuperView = nil;

    while (nil != superView && nil == foundSuperView) {
        if ([superView isKindOfClass:[UITableViewCell class]]) {
            foundSuperView = superView;
        } else {
            superView = superView.superview;
        }
    }

El enlace tiene código para una solución más reutilizable, pero esto debería funcionar.

Stenio Ferreira
fuente
6

Swift 2.2

Necesita agregar objetivo para ese botón.

myButton.addTarget(self, action: #selector(ClassName.FunctionName(_:), forControlEvents: .TouchUpInside)

FunctionName: conectado // por ejemplo

Y, por supuesto, debe configurar la etiqueta de ese botón ya que lo está utilizando.

myButton.tag = indexPath.row

Puede lograr esto subclasificando UITableViewCell. Úselo en el generador de interfaces, suelte un botón en esa celda, conéctelo a través de una toma de corriente y listo.

Para obtener la etiqueta en la función conectada:

func connected(sender: UIButton) {
    let buttonTag = sender.tag
    // Do any additional setup
}
Himanshu padia
fuente
6

Swift 3 con cierre

Una buena solución es usar un cierre en un UITableViewCell personalizado para devolver la llamada al viewController para una acción.

En la celda:

final class YourCustomCell: UITableViewCell {

    var callbackClosure: (() -> Void)?

    // Configure the cell here
    func configure(object: Object, callbackClosure: (() -> Void)?) {
       self.callbackClosure = callbackClosure
    }


// MARK: - IBAction
extension YourCustomCell {
    @IBAction fileprivate func actionPressed(_ sender: Any) {
        guard let closure = callbackClosure else { return }
        closure()
    }
}

En el controlador de vista: delegado de vista de tabla

extension YourViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        guard let cell: YourCustomCell = cell as? YourCustomCell else { return }
        cell.configure(object: object, callbackClosure: { [weak self] in
            self?.buttonAction()
        })
     }
 }

fileprivate extension YourViewController {

    func buttonAction() {
        // do your actions here 
    }
}
Sevy11
fuente
5

Me parece más simple subclasificar el botón dentro de su celda (Swift 3):

class MyCellInfoButton: UIButton {
    var indexPath: IndexPath?
}

En tu clase celular:

class MyCell: UICollectionViewCell {
    @IBOutlet weak var infoButton: MyCellInfoButton!
   ...
}

En la fuente de datos de la vista de tabla o de la vista de colección, al quitar la celda de la celda, dele al botón su ruta de índice:

cell.infoButton.indexPath = indexPath

Entonces puede simplemente poner este código en su controlador de vista de tabla:

@IBAction func handleTapOnCellInfoButton(_ sender: MyCellInfoButton) {
        print(sender.indexPath!) // Do whatever you want with the index path!
}

¡Y no olvide configurar la clase del botón en su Interface Builder y vincularlo a la handleTapOnCellInfoButtonfunción!


editado:

Usando inyección de dependencia. Para configurar una llamada de cierre:

class MyCell: UICollectionViewCell {
    var someFunction: (() -> Void)?
    ...
    @IBAction func didTapInfoButton() {
        someFunction?()
    }
}

e inyecte el cierre en el método willDisplay del delegado de la vista de colección:

 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    (cell as? MyCell)?.someFunction = {
        print(indexPath) // Do something with the indexPath.
    }
}
yesleon
fuente
El enfoque de cierre es la forma más rápida que he visto para hacer esto. ¡Buen trabajo!
Clifton Labrum
5

Es trabajo para mí.

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     UIButton *Btn_Play = (UIButton *)[cell viewWithTag:101];
     [Btn_Play addTarget:self action:@selector(ButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)ButtonClicked:(UIButton*)sender {
     CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.Tbl_Name];
     NSIndexPath *indexPath = [self.Tbl_Name indexPathForRowAtPoint:buttonPosition];
}
Yogesh Tarsariya
fuente
1
// Add action in cell for row at index path -tableView

cell.buttonName.addTarget(self, action: #selector(ViewController.btnAction(_:)), for: .touchUpInside)

// Button Action

  @objc func btnAction(_ sender: AnyObject) {



        var position: CGPoint = sender.convert(.zero, to: self.tableView)


        let indexPath = self.tableView.indexPathForRow(at: position)
        let cell: UITableViewCell = tableView.cellForRow(at: indexPath!)! as
        UITableViewCell




}
Hitesh Chauhan
fuente
1

para swift 4:

inside the cellForItemAt ,
   
cell.chekbx.addTarget(self, action: #selector(methodname), for: .touchUpInside)

then outside of cellForItemAt
@objc func methodname()
{
//your function code
}

Radhe Yadav
fuente
1

Si desea pasar el valor del parámetro de la celda a UIViewController usando el cierre, entonces

//Your Cell Class
class TheCell: UITableViewCell {

    var callBackBlockWithParam: ((String) -> ()) = {_ in }

//Your Action on button
    @IBAction func didTap(_ sender: Any) {
        callBackBlockWithParam("Your Required Parameter like you can send button as sender or anything just change parameter type. Here I am passing string")
    }
}

//Your Controller
extension TheController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: TheCell.identifier, for: indexPath) as! TheCell {
            cell.callBackBlockWithParam = { (passedParamter) in 

             //you will get string value from cell class
                print(passedParamter)     
      }
            return cell
    }
}
prachit
fuente
0

La respuesta de @Mani es buena, sin embargo, las etiquetas de vistas dentro de contentView de la celda a menudo se usan para otros fines. En su lugar, puede usar la etiqueta de la celda (o la etiqueta contentView de la celda):

1) En su cellForRowAtIndexPath:método, asigne la etiqueta de la celda como índice:

cell.tag = indexPath.row; // or cell.contentView.tag...

2) Agregue objetivo y acción para su botón de la siguiente manera:

[cell.yourbutton addTarget:self action:@selector(yourButtonClicked:) forControlEvents:UIControlEventTouchUpInside];

3) Crear método que devuelve la fila del remitente (gracias @Stenio Ferreira):

- (NSInteger)rowOfSender:(id)sender
{
    UIView *superView = sender.superview;
    while (superView) {
        if ([superView isKindOfClass:[UITableViewCell class]])
            break;
        else
            superView = superView.superview;
    }

    return superView.tag;
}

4) Acciones de código basadas en el índice:

-(void)yourButtonClicked:(UIButton*)sender
{
     NSInteger index = [self rowOfSender:sender];
     // Your code here
}
Borzh
fuente
0

CustomTableCell.h es un UITableViewCell:

@property (weak, nonatomic) IBOutlet UIButton *action1Button;
@property (weak, nonatomic) IBOutlet UIButton *action2Button;

MyVC.m después de las importaciones:

@interface MYTapGestureRecognizer : UITapGestureRecognizer
@property (nonatomic) NSInteger dataint;
@end

Dentro de "cellForRowAtIndexPath" en MyVC.m:

//CustomTableCell 
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

//Set title buttons
[cell.action1Button setTitle:[NSString stringWithString:NSLocalizedString(@"action1", nil)] forState:UIControlStateNormal];
[cell.action2Button setTitle:[NSString stringWithString:NSLocalizedString(@"action2", nil)] forState:UIControlStateNormal];

//Set visibility buttons
[cell.action1Button setHidden:FALSE];
[cell.action2Button setHidden:FALSE];

//Do 1 action
[cell.action1Button addTarget:self action:@selector(do1Action :) forControlEvents:UIControlEventTouchUpInside];

//Do 2 action
MYTapGestureRecognizer *action2Tap = [[MYTapGestureRecognizer alloc] initWithTarget:self action:@selector(do2Action :)];
cancelTap.numberOfTapsRequired = 1;
cancelTap.dataint = indexPath.row;
[cell.action2Button setUserInteractionEnabled:YES];
[cell.action2Button addGestureRecognizer:action2Tap];

MyVC.m:

-(void)do1Action :(id)sender{
//do some action that is not necessary fr data
}

-(void)do2Action :(UITapGestureRecognizer *)tapRecognizer{
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
numberTag = tap.dataint;
FriendRequest *fr = [_list objectAtIndex:numberTag];

//connect with a WS o do some action with fr data

//actualize list in tableView
 [self.myTableView reloadData];
}
Mer
fuente
-1
cell.show.tag=indexPath.row;
     [cell.show addTarget:self action:@selector(showdata:) forControlEvents:UIControlEventTouchUpInside];

-(IBAction)showdata:(id)sender
{
    UIButton *button = (UIButton *)sender;

    UIStoryboard *storyBoard;
    storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    SecondViewController *detailView = [storyBoard instantiateViewControllerWithIdentifier:@"SecondViewController"];

    detailView.string=[NSString stringWithFormat:@"%@",[_array objectAtIndex:button.tag]];

    [self presentViewController:detailView animated:YES completion:nil];

}
usuario8132169
fuente