Detectar qué UIButton se presionó en un UITableView

212

Tengo un UITableViewcon 5 UITableViewCells. Cada celda contiene una UIButtonque se configura de la siguiente manera:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     NSString *identifier = @"identifier";
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
     if (cell == nil) {
         cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
         [cell autorelelase];

         UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 5, 40, 20)];
         [button addTarget:self action:@selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
         [button setTag:1];
         [cell.contentView addSubview:button];

         [button release];
     }

     UIButton *button = (UIButton *)[cell viewWithTag:1];
     [button setTitle:@"Edit" forState:UIControlStateNormal];

     return cell;
}

Mi pregunta es esta: en el buttonPressedAction:método, ¿cómo sé qué botón se ha presionado? He considerado usar etiquetas, pero no estoy seguro de que esta sea la mejor ruta. Me gustaría poder etiquetar de alguna manera indexPathel control.

- (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    // how do I know which button sent this message?
    // processing button press for this row requires an indexPath. 
}

¿Cuál es la forma estándar de hacer esto?

Editar:

Lo resolví un poco haciendo lo siguiente. Todavía me gustaría tener una opinión si esta es la forma estándar de hacerlo o si hay una mejor manera?

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     NSString *identifier = @"identifier";
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
     if (cell == nil) {
         cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
         [cell autorelelase];

         UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 5, 40, 20)];
         [button addTarget:self action:@selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
         [cell.contentView addSubview:button];

         [button release];
     }

     UIButton *button = (UIButton *)[cell.contentView.subviews objectAtIndex:0];
     [button setTag:indexPath.row];
     [button setTitle:@"Edit" forState:UIControlStateNormal];

     return cell;
}

- (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    int row = button.tag;
}

Lo que es importante tener en cuenta es que no puedo configurar la etiqueta en la creación de la celda, ya que la celda podría quedar en espera. Se siente muy sucio. Debe haber una mejor manera.

rienda
fuente
No veo ningún problema con el uso de su solución de etiqueta. Las celdas se reutilizan, por lo que tiene sentido establecer la etiqueta en el índice de fila de la forma en que lo está haciendo aquí. Me parece una solución mucho más elegante que convertir la ubicación táctil en un índice de fila, como se sugiere a continuación.
Erik van der Neut

Respuestas:

400

En la muestra de accesorios de Apple se utiliza el siguiente método:

[button addTarget:self action:@selector(checkButtonTapped:) forControlEvents:UIControlEventTouchUpInside];

Luego, en el controlador táctil, la coordenada táctil recuperada y la ruta del índice se calcula a partir de esa coordenada:

- (void)checkButtonTapped:(id)sender
{
    CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     ...
    }
}
Vladimir
fuente
Sí, esto es lo que decidí (ver mi edición). Estoy de acuerdo contigo en que no es óptimo.
rienda
2
Pero agrega UIButton a UITableViewCell usted mismo, por lo que debe ser coherente con lo que hace al crear la celda. Aunque este enfoque no parece realmente elegante, debo admitirlo
Vladimir,
1
Para la primera solución, necesitará tomar [[superview de botón] superview] ya que la primera llamada de superview le dará el contentView, y finalmente el segundo le dará el UITableViewCell. La segunda solución no funciona bien si agrega / elimina celdas, ya que invalidará el índice de fila. Por lo tanto, fui con la primera solución como se describe y funcionó perfectamente.
Raidfive
3
Esto seleccionará de manera confiable la celda que posee el botón: UIView * view = button; while (! [view isKindOfClass: [clase UITableViewCell]]) {view = [view superview]}
Jacob Lyles
1
Hay una trampa cuando se usa: [button addTarget: self action: @selector (checkButtonTapped :) forControlEvents: UIControlEventTouchUpInside]; porque addTarget: action: forControlEvents: agregará múltiples objetivos y acciones duplicados cuando desplaza la tabla, no eliminará los objetivos y acciones anteriores, por lo que el método checkButtonTapped: se llamará muchas veces cuando haga clic en el botón. Será mejor que elimines el objetivo y la acción antes de agregarlos
bandw
48

Encontré que el método de usar la supervista de la supervista para obtener una referencia al indexPath de la celda funcionó perfectamente. Gracias a iphonedevbook.com (macnsmith) por el texto del enlace de sugerencia

-(void)buttonPressed:(id)sender {
 UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
 NSIndexPath *clickedButtonPath = [self.tableView indexPathForCell:clickedCell];
...

}
Coco
fuente
Cocoanut, su fragmento de código me señaló en la dirección correcta para mi propia variación de este problema. ¡Gracias! En caso de que alguien más lo necesite, mi caso especial fue que el botón estaba en una celda personalizada que se mostraba como parte del pie de página. Agregaré el código a continuación
software evolucionó
Si usted (lector de Stackoverflow) intenta esto y no funciona para usted, verifique si en su implementación su UIButton es realmente el nieto de su UITableViewCell. En mi implementación, mi UIButton era un hijo directo de mi UITableViewCell, por lo que necesitaba sacar una de las "supervistas" en el código de Cocoanut, y luego funcionó.
Jon Schneider
29
Esto está muy, muy mal y está roto en las nuevas versiones del sistema operativo. No camines por árboles con vistas que no te pertenecen.
Kenrik marzo
2
Esto funcionaba para mí en iOS 6, pero está roto en iOS 7. ¡Parece que @KenrikMarch tiene un punto válido!
Jon Schneider
3
en iOS 7 es un paso más en la supervista. por ejemplo, [[[remitente superview] superview] superView];
CW0007007
43

Así es como lo hago. Simple y conciso:

- (IBAction)buttonTappedAction:(id)sender
{
    CGPoint buttonPosition = [sender convertPoint:CGPointZero
                                           toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    ...
}
Chris Schwerdt
fuente
2
Aún más simple: usar en CGPointZerolugar de CGPointMake(0, 0);-)
Jakob W
Fácil de trabajar con él. Además, es fácil traducirlo a Swift 3. Eres el mejor :)
Francisco Romero
Traducido a Swift abajo. La solución más fácil que pude encontrar. Gracias Chris!
Rutger Huijsmans
6

Encontré una buena solución a este problema en otro lugar, sin perder el tiempo con las etiquetas en el botón:

- (void)buttonPressedAction:(id)sender {

    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    CGPoint currentTouchPosition = [touch locationInView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];

    // do stuff with the indexPath...
}
Alpinista
fuente
55
No está claro en este ejemplo de dónde obtiene el objeto 'evento'.
Nick Ludlam
Esta es la solución con la que fui. El uso de etiquetas es impredecible al agregar / eliminar filas, ya que sus índices cambian. También,
raidfive
@NickLudlam: probablemente el nombre del método no es buttonPressedAction:pero buttonPressedAction:forEvent:.
KPM
5

¿Qué tal enviar la información como NSIndexPathen el UIButtonuso de inyección en tiempo de ejecución?

1) Necesita tiempo de ejecución en la importación

2) agregar constante estática

3) agregue NSIndexPatha su botón en tiempo de ejecución usando:

(nulo) setMetaData: (id) objetivo conObject: (id) newObj

4) al presionar el botón obtener metadatos usando:

(id) metaData: (id) objetivo

Disfrutar

    #import <objc/runtime.h>
    static char const * const kMetaDic = "kMetaDic";


    #pragma mark - Getters / Setters

- (id)metaData:(id)target {
    return objc_getAssociatedObject(target, kMetaDic);
}

- (void)setMetaData:(id)target withObject:(id)newObj {
    objc_setAssociatedObject(target, kMetaDic, newObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}



    #On the cell constructor
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    ....
    cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    ....
    [btnSocial addTarget:self
                                   action:@selector(openComments:)
                         forControlEvents:UIControlEventTouchUpInside];

    #add the indexpath here or another object
    [self setMetaData:btnSocial withObject:indexPath];

    ....
    }



    #The action after button been press:

    - (IBAction)openComments:(UIButton*)sender{

        NSIndexPath *indexPath = [self metaData:sender];
        NSLog(@"indexPath: %d", indexPath.row);

        //Reuse your indexpath Now
    }
magno cardona
fuente
1
SI la tabla se reorganiza o se elimina una fila, entonces esto no funcionará.
Neil
5

La respuesta de (@Vladimir) es Swift:

var buttonPosition = sender.convertPoint(CGPointZero, toView: self.tableView)
var indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)!

Aunque la comprobación indexPath != nilme da el dedo ... "NSIndexPath no es un subtipo de NSString"

dennis
fuente
5

Con Swift 4.2 y iOS 12, puede elegir uno de los 5 ejemplos completos siguientes para resolver su problema.


# 1 El uso de UIView's convert(_:to:)y UITableView' sindexPathForRow(at:)

import UIKit

private class CustomCell: UITableViewCell {

    let button = UIButton(type: .system)

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        button.setTitle("Tap", for: .normal)
        contentView.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
        button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.button.addTarget(self, action: #selector(customCellButtonTapped), for: .touchUpInside)
        return cell
    }

    @objc func customCellButtonTapped(_ sender: UIButton) {
        let point = sender.convert(CGPoint.zero, to: tableView)
        guard let indexPath = tableView.indexPathForRow(at: point) else { return }
        print(indexPath)
    }

}

# 2 El uso de UIView's convert(_:to:)y UITableView' s indexPathForRow(at:)(alternativa)

Esta es una alternativa al ejemplo anterior donde pasamos nilal targetparámetro en addTarget(_:action:for:). De esta manera, si el primer respondedor no implementa la acción, se enviará al siguiente respondedor en la cadena del respondedor hasta que se encuentre una implementación adecuada.

import UIKit

private class CustomCell: UITableViewCell {

    let button = UIButton(type: .system)

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        button.setTitle("Tap", for: .normal)
        button.addTarget(nil, action: #selector(TableViewController.customCellButtonTapped), for: .touchUpInside)
        contentView.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
        button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        return cell
    }

    @objc func customCellButtonTapped(_ sender: UIButton) {
        let point = sender.convert(CGPoint.zero, to: tableView)
        guard let indexPath = tableView.indexPathForRow(at: point) else { return }
        print(indexPath)
    }

}

# 3 Usando UITableView's indexPath(for:)y patrón de delegado

En este ejemplo, configuramos el controlador de vista como el delegado de la celda. Cuando se toca el botón de la celda, se activa una llamada al método apropiado del delegado.

import UIKit

protocol CustomCellDelegate: AnyObject {
    func customCellButtonTapped(_ customCell: CustomCell)
}

class CustomCell: UITableViewCell {

    let button = UIButton(type: .system)
    weak var delegate: CustomCellDelegate?

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        button.setTitle("Tap", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        contentView.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
        button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @objc func buttonTapped(sender: UIButton) {
        delegate?.customCellButtonTapped(self)
    }

}
import UIKit

class TableViewController: UITableViewController, CustomCellDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

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

    // MARK: - CustomCellDelegate

    func customCellButtonTapped(_ customCell: CustomCell) {
        guard let indexPath = tableView.indexPath(for: customCell) else { return }
        print(indexPath)
    }

}

# 4. Uso de UITableView'sy indexPath(for:)un cierre para delegación

Esta es una alternativa al ejemplo anterior donde usamos un cierre en lugar de una declaración de delegado de protocolo para manejar el toque del botón.

import UIKit

class CustomCell: UITableViewCell {

    let button = UIButton(type: .system)
    var buttontappedClosure: ((CustomCell) -> Void)?

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        button.setTitle("Tap", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        contentView.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
        button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @objc func buttonTapped(sender: UIButton) {
        buttontappedClosure?(self)
    }

}
import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.buttontappedClosure = { [weak tableView] cell in
            guard let indexPath = tableView?.indexPath(for: cell) else { return }
            print(indexPath)
        }
        return cell
    }

}

# 5. El uso de UITableViewCell's accessoryTypey UITableViewDelegate' stableView(_:accessoryButtonTappedForRowWith:)

Si su botón es un UITableViewCellcontrol accesorio estándar, cualquier toque en él activará una llamada a UITableViewDelegate"s" tableView(_:accessoryButtonTappedForRowWith:), lo que le permitirá obtener la ruta de índice relacionada.

import UIKit

private class CustomCell: UITableViewCell {

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        accessoryType = .detailButton
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        return cell
    }

    override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
        print(indexPath)
    }

}
Imanou Petit
fuente
5
func buttonAction(sender:UIButton!)
    {
        var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tablevw)
        let indexPath = self.tablevw.indexPathForRowAtPoint(position)
        let cell: TableViewCell = tablevw.cellForRowAtIndexPath(indexPath!) as TableViewCell
        println(indexPath?.row)
        println("Button tapped")
    }
Ankit Bansal
fuente
3

Usaría la propiedad de etiqueta como dijiste, configurando la etiqueta de esta manera:

[button setTag:indexPath.row];

luego obtener la etiqueta dentro del botónPressedAction así:

((UIButton *)sender).tag

O

UIButton *button = (UIButton *)sender; 
button.tag;
ACBurk
fuente
55
Este enfoque está completamente roto para tablas con secciones.
ohhorob
no, también podrías usar alguna función simple para poner la sección en la etiqueta.
ACBurk
2
tages un entero Parece un poco torpe codificar / decodificar rutas de índice en etiquetas de vista.
ohhorob
Eso es correcto, pero es una solución, aunque no una que usaría si tuviera secciones. Todo lo que intentaba decir era que se podía hacer usando este método, que no estaba roto. Una versión mejor y más compleja determinaría el indexpath desde la posición del botón dentro de UITableView. Sin embargo, dado que Rein ha dicho que solo tiene cinco celdas (sin secciones), probablemente haga que ese método sea demasiado complicado y su comentario inicial y todo este hilo de comentarios carezca de sentido.
ACBurk
3

Aunque me gusta la forma de la etiqueta ... si no desea usar etiquetas por cualquier razón, puede crear un miembro NSArrayde botones prefabricados:

NSArray* buttons ;

luego cree esos botones antes de representar el TableView y empújelos en la matriz.

Luego dentro de la tableView:cellForRowAtIndexPath:función puedes hacer:

UIButton* button = [buttons objectAtIndex:[indexPath row] ] ;
[cell.contentView addSubview:button];

Luego en la buttonPressedAction:función, puedes hacer

- (void)buttonPressedAction:(id)sender {
   UIButton* button = (UIButton*)sender ;
   int row = [buttons indexOfObject:button] ;
   // Do magic
}
Vejez
fuente
2

PARA MANEJAR SECCIONES: almacené NSIndexPath en un UITableViewCell personalizado

IN CLKIndexPricesHEADERTableViewCell.xib

IN IB Agregue UIButton a XIB - ¡NO agregue acción!

Agregar salida @property (retención, no atómica) IBOutlet UIButton * buttonIndexSectionClose;

NO CTRL + ARRASTRE una acción en IB (hecho en el código a continuación)

@interface CLKIndexPricesHEADERTableViewCell : UITableViewCell
...
@property (retain, nonatomic) IBOutlet UIButton *buttonIndexSectionClose;
@property (nonatomic, retain) NSIndexPath * indexPathForCell;
@end

En viewForHeaderInSection (también debería funcionar para cellForRow ... etc si su tabla tiene solo 1 sección)

- viewForHeaderInSection is called for each section 1...2...3
- get the cell CLKIndexPricesHEADERTableViewCell 
- getTableRowHEADER just does the normal dequeueReusableCellWithIdentifier
- STORE the indexPath IN the UITableView cell
- indexPath.section = (NSInteger)section
- indexPath.row = 0 always (we are only interested in sections)

- (UIView *) tableView:(UITableView *)tableView1 viewForHeaderInSection:(NSInteger)section {


    //Standard method for getting a UITableViewCell
    CLKIndexPricesHEADERTableViewCell * cellHEADER = [self getTableRowHEADER];

... use la sección para obtener datos para su celda

...rellenarlo

   indexName        = ffaIndex.routeCode;
   indexPrice       = ffaIndex.indexValue;

   //

   [cellHEADER.buttonIndexSectionClose addTarget:self
                                          action:@selector(buttonDELETEINDEXPressedAction:forEvent:)
                                forControlEvents:UIControlEventTouchUpInside];


   cellHEADER.indexPathForCell = [NSIndexPath indexPathForRow:0 inSection:section];


    return cellHEADER;
}

El USUARIO presiona el botón ELIMINAR en un encabezado de sección y esto llama

- (void)buttonDELETEINDEXPressedAction:(id)sender forEvent:(UIEvent *)event
{
    NSLog(@"%s", __PRETTY_FUNCTION__);


    UIView *  parent1 = [sender superview];   // UiTableViewCellContentView
    //UIView *myContentView = (UIView *)parent1;

    UIView *  parent2 = [parent1 superview];  // custom cell containing the content view
    //UIView *  parent3 = [parent2 superview];  // UITableView containing the cell
    //UIView *  parent4 = [parent3 superview];  // UIView containing the table


    if([parent2 isMemberOfClass:[CLKIndexPricesHEADERTableViewCell class]]){
        CLKIndexPricesHEADERTableViewCell *myTableCell = (CLKIndexPricesHEADERTableViewCell *)parent2;

        //UITableView *myTable = (UITableView *)parent3;
        //UIView *mainView = (UIView *)parent4;

        NSLog(@"%s indexPath.section,row[%d,%d]", __PRETTY_FUNCTION__, myTableCell.indexPathForCell.section,myTableCell.indexPathForCell.row);

        NSString *key = [self.sortedKeysArray objectAtIndex:myTableCell.indexPathForCell.section];
        if(key){
            NSLog(@"%s DELETE object at key:%@", __PRETTY_FUNCTION__,key);
            self.keyForSectionIndexToDelete = key;
            self.sectionIndexToDelete = myTableCell.indexPathForCell.section;

            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Remove Index"
                                                                message:@"Are you sure"
                                                               delegate:self
                                                      cancelButtonTitle:@"No"
                                                      otherButtonTitles:@"Yes", nil];
            alertView.tag = kALERTVIEW_REMOVE_ONE_INDEX;
            [alertView show];
            [alertView release];
            //------
        }else{
            NSLog(@"ERROR: [%s] key is nil for section:%d", __PRETTY_FUNCTION__,myTableCell.indexPathForCell.section);
        }

    }else{
        NSLog(@"ERROR: [%s] CLKIndexPricesHEADERTableViewCell not found", __PRETTY_FUNCTION__);
    }
}

En este ejemplo, agregué un botón Eliminar, por lo que debería mostrar UIAlertView para confirmarlo

Guardo la sección y la clave en el diccionario almacenando información sobre la sección en un ivar en el VC

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
   if(alertView.tag == kALERTVIEW_REMOVE_ONE_INDEX){
        if(buttonIndex==0){
            //NO
            NSLog(@"[%s] BUTTON:%d", __PRETTY_FUNCTION__,buttonIndex);
            //do nothing
        }
        else if(buttonIndex==1){
            //YES
            NSLog(@"[%s] BUTTON:%d", __PRETTY_FUNCTION__,buttonIndex);
            if(self.keyForSectionIndexToDelete != nil){

                //Remove the section by key
                [self.indexPricesDictionary removeObjectForKey:self.keyForSectionIndexToDelete];

                //sort the keys so sections appear alphabetically/numbericsearch (minus the one we just removed)
                [self updateTheSortedKeysArray];                

                //Delete the section from the table using animation
                [self.tableView beginUpdates];

                [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:self.sectionIndexToDelete]
                              withRowAnimation:UITableViewRowAnimationAutomatic];
                [self.tableView endUpdates];

                //required to trigger refresh of myTableCell.indexPathForCell else old values in UITableViewCells
                [self.tableView reloadData];
            }else{
                NSLog(@"ERROR: [%s] OBJECT is nil", __PRETTY_FUNCTION__);
            }
        }
        else {
            NSLog(@"ERROR: [%s] UNHANDLED BUTTON:%d", __PRETTY_FUNCTION__,buttonIndex);
        }
    }else {
        NSLog(@"ERROR: [%s] unhandled ALERTVIEW TAG:%d", __PRETTY_FUNCTION__,alertView.tag);
    }
}
brian.clear
fuente
2
A better way would be to subclass your button and add a indexPath property to it.

//Implement a subclass for UIButton.

@interface NewButton:UIButton
@property(nonatomic, strong) NSIndexPath *indexPath;


Make your button of type NewButton in the XIB or in the code whereever you are initializing them.

Then in the cellForRowAtIndexPath put the following line of code.

button.indexPath = indexPath;

return cell; //As usual



Now in your IBAction

-(IBAction)buttonClicked:(id)sender{
   NewButton *button = (NewButton *)sender;

//Now access the indexPath by buttons property..

   NSIndexPath *indexPath = button.indexPath; //:)
}
mmmanishs
fuente
Esto es un poco defectuoso porque indexPath de una celda puede cambiar, si llama a deleteRowsAtIndexPaths.
John Gibb
deleteRowsAtIndexPaths hará que cellForRowAtIndexPath sea llamado nuevamente. Entonces los botones tendrán nuevos indexPaths correctos.
mmmanishs
1

A mí también me funciona, gracias @Cocoanut

Encontré que el método de usar la supervista de la supervista para obtener una referencia al indexPath de la celda funcionó perfectamente. Gracias a iphonedevbook.com (macnsmith) por el texto del enlace de sugerencia

-(void)buttonPressed:(id)sender {
 UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
 NSIndexPath *clickedButtonPath = [self.tableView indexPathForCell:clickedCell];
...

}
usuario366584
fuente
0

puedes usar el patrón de etiqueta:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     NSString *identifier = @"identifier";
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
     if (cell == nil) {
         cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
         [cell autorelelase];

         UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 5, 40, 20)];
         [button addTarget:self action:@selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
         [button setTag:[indexPath row]]; //use the row as the current tag
         [cell.contentView addSubview:button];

         [button release];
     }

     UIButton *button = (UIButton *)[cell viewWithTag:[indexPath row]]; //use [indexPath row]
     [button setTitle:@"Edit" forState:UIControlStateNormal];

     return cell;
}

- (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    //button.tag has the row number (you can convert it to indexPath)
}
Nir Levy
fuente
¿Cómo etiquetaría los controles si tuviera múltiples controles en una sola celda?
rienda
No estoy seguro de que esto funcione: si la celda se crea para la fila n. ° 1, obtendrá la etiqueta 1. Si se retira de la fila n. ° 3, seguirá teniendo una etiqueta de 1, no 3.
rein
Supongo que tienes razón sobre el segundo comentario. culpa mía. Creo que su mejor solución es subclasificar UIButton, agregar otra propiedad o dos propias y luego configurarlas / obtenerlas en los casos apropiados (quédese con la etiqueta: 1 que tenía en su código)
Nir Levy
0

¿Me estoy perdiendo de algo? ¿No puedes usar el remitente para identificar el botón? El remitente te dará información como esta:

<UIButton: 0x4b95c10; frame = (246 26; 30 30); opaque = NO; tag = 104; layer = <CALayer: 0x4b95be0>>

Luego, si desea cambiar las propiedades del botón, diga la imagen de fondo que acaba de decirle al remitente:

[sender setBackgroundImage:[UIImage imageNamed:@"new-image.png"] forState:UIControlStateNormal];

Si necesita la etiqueta, entonces el método de ACBurk está bien.

Michael Morrison
fuente
1
Están buscando su "objeto" con el que se relaciona el botón
ohhorob
0
// how do I know which button sent this message?
// processing button press for this row requires an indexPath.

Bastante sencillo en realidad:

- (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    CGPoint rowButtonCenterInTableView = [[rowButton superview] convertPoint:rowButton.center toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rowButtonCenterInTableView];
    MyTableViewItem *rowItem = [self.itemsArray objectAtIndex:indexPath.row];
    // Now you're good to go.. do what the intention of the button is, but with
    // the context of the "row item" that the button belongs to
    [self performFooWithItem:rowItem];
}

Trabajando bien para mí: P

si desea ajustar su configuración de acción de destino, puede incluir el parámetro de evento en el método y luego usar los toques de ese evento para resolver las coordenadas del toque. Las coordenadas aún deben resolverse en los límites de la vista táctil, pero eso puede parecer más fácil para algunas personas.

ohhorob
fuente
0

cree una matriz nsmutable y coloque todos los botones en esa matriz usint [array addObject: yourButton];

en el método de presionar el botón

-

 (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;

for(int i=0;i<[yourArray count];i++){

if([buton isEqual:[yourArray objectAtIndex:i]]){

//here write wat u need to do

}
}
rajesh
fuente
0

Una ligera variación en la respuesta de Cocoanuts (que me ayudó a resolver esto) cuando el botón estaba en el pie de página de una tabla (lo que le impide encontrar la 'celda en la que se hizo clic':

-(IBAction) buttonAction:(id)sender;
{
    id parent1 = [sender superview];   // UiTableViewCellContentView
    id parent2 = [parent1 superview];  // custom cell containing the content view
    id parent3 = [parent2 superview];  // UITableView containing the cell
    id parent4 = [parent3 superview];  // UIView containing the table

    UIView *myContentView = (UIView *)parent1;
    UITableViewCell *myTableCell = (UITableViewCell *)parent2;
    UITableView *myTable = (UITableView *)parent3;
    UIView *mainView = (UIView *)parent4;

    CGRect footerViewRect = myTableCell.frame;
    CGRect rect3 = [myTable convertRect:footerViewRect toView:mainView];    

    [cc doSomethingOnScreenAtY:rect3.origin.y];
}
software evolucionado
fuente
0

Siempre uso etiquetas.

Debe subclasificar UITableviewCelly manejar el botón presionar desde allí.

Chris
fuente
No entiendo muy bien cómo. La propiedad de etiqueta se configura durante la creación de la celda: esta celda es reutilizable para cada fila con el mismo identificador. Esta etiqueta es específica para el control en una celda reutilizable genérica. ¿Cómo puedo usar esta etiqueta para diferenciar botones en celdas que se crearon de forma genérica? ¿Podría publicar algún código?
rienda
0

Es sencillo; crea una celda personalizada y toma una salida de botón

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
         NSString *identifier = @"identifier";
        customCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    cell.yourButton.tag = indexPath.Row;

- (void)buttonPressedAction:(id)sender

cambiar la identificación en el método anterior a (UIButton *)

Puede obtener el valor de qué botón está siendo tocado haciendo sender.tag.

piyush Bageria
fuente
0

Subclase el botón para almacenar el valor requerido, tal vez cree un protocolo (ControlWithData o algo así). Establezca el valor cuando agregue el botón a la celda de vista de tabla. En su evento de retoque, vea si el remitente obedece el protocolo y extraiga los datos. Normalmente almaceno una referencia al objeto real que se representa en la celda de la vista de tabla.

Jerome Chan Yeow Heong
fuente
0

ACTUALIZACIÓN DE SWIFT 2

¡Aquí se explica cómo averiguar qué botón se tocó + enviar datos a otro ViewController desde ese botón, indexPath.rowya que supongo que ese es el punto para la mayoría!

@IBAction func yourButton(sender: AnyObject) {


     var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
        let indexPath = self.tableView.indexPathForRowAtPoint(position)
        let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath!)! as
        UITableViewCell
        print(indexPath?.row)
        print("Tap tap tap tap")

    }

Para aquellos que usan una clase ViewController y agregaron un TableView, estoy usando un ViewController en lugar de un TableViewController, así que agregué manualmente el TableView para acceder a él.

Aquí está el código para pasar datos a otro VC al tocar ese botón y pasar la celda indexPath.row

@IBAction func moreInfo(sender: AnyObject) {

    let yourOtherVC = self.storyboard!.instantiateViewControllerWithIdentifier("yourOtherVC") as! YourOtherVCVIewController



    var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
    let indexPath = self.tableView.indexPathForRowAtPoint(position)
    let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath!)! as
    UITableViewCell
    print(indexPath?.row)
    print("Button tapped")


    yourOtherVC.yourVarName = [self.otherVCVariable[indexPath!.row]]

    self.presentViewController(yourNewVC, animated: true, completion: nil)

}
Lukesivi
fuente
0

Tenga en cuenta que estoy usando una celda personalizada, este código funciona perfectamente para mí

 @IBAction func call(sender: UIButton)
    {
        var contentView = sender.superview;
        var cell = contentView?.superview as EmployeeListCustomCell
        if (!(cell.isKindOfClass(EmployeeListCustomCell)))
        {
            cell = (contentView?.superview)?.superview as EmployeeListCustomCell
        }

        let phone = cell.lblDescriptionText.text!
        //let phone = detailObject!.mobile!
        let url:NSURL = NSURL(string:"tel://"+phone)!;
        UIApplication.sharedApplication().openURL(url);
    }
Gaurav
fuente
0

La solución de Chris Schwerdt pero luego en Swift funcionó para mí:

@IBAction func rateButtonTapped(sender: UIButton) {
    let buttonPosition : CGPoint = sender.convertPoint(CGPointZero, toView: self.ratingTableView)
    let indexPath : NSIndexPath = self.ratingTableView.indexPathForRowAtPoint(buttonPosition)!

    print(sender.tag)
    print(indexPath.row)
}
Rutger Huijsmans
fuente
0

Este problema tiene dos partes:

1) Obtener la ruta del índice UITableViewCellque contiene presionadoUIButton

Hay algunas sugerencias como:

  • Actualizando UIButton's tagen cellForRowAtIndexPath:el método de uso de ruta del índice rowde valor. Esta no es una buena solución, ya que requiere una actualización tagcontinua y no funciona con vistas de tabla con más de una sección.

  • Adición de una NSIndexPathpropiedad de celda personalizado y su actualización en lugar de UIButton's tagen el cellForRowAtIndexPath:método. Esto resuelve el problema de múltiples secciones, pero aún no es bueno, ya que requiere una actualización siempre.

  • Mantener una referencia débil al elemento primario UITableViewen la celda personalizada mientras se crea y se usa el indexPathForCell:método para obtener la ruta del índice. Parece un poco mejor, no es necesario actualizar nada en el cellForRowAtIndexPath:método, pero aún requiere establecer una referencia débil cuando se crea la celda personalizada.

  • Usar la superViewpropiedad de la celda para obtener una referencia a padre UITableView. No es necesario agregar ninguna propiedad a la celda personalizada, y no es necesario establecer / actualizar nada en la creación / posterior. Pero la célula superViewdepende de los detalles de implementación de iOS. Por lo tanto, no se puede usar directamente.

Pero esto se puede lograr usando un bucle simple, ya que estamos seguros de que la celda en cuestión debe estar en un UITableView:

UIView* view = self;
while (view && ![view isKindOfClass:UITableView.class])
    view = view.superview;
UITableView* parentTableView = (UITableView*)view;

Por lo tanto, estas sugerencias se pueden combinar en un método de celda personalizado simple y seguro para obtener la ruta del índice:

- (NSIndexPath *)indexPath
{
    UIView* view = self;

    while (view && ![view isKindOfClass:UITableView.class])
        view = view.superview;

    return [(UITableView*)view indexPathForCell:self];
}

De ahora en adelante, este método puede usarse para detectar cuál UIButtonse presiona.

2) Informar a otras partes sobre el evento de presionar un botón

Después de saber internamente qué UIButtonse presiona en qué celda personalizada con la ruta de índice exacta, esta información debe enviarse a otras partes (lo más probable es que el controlador de vista maneje el UITableView). Por lo tanto, este evento de clic de botón se puede manejar en un nivel de abstracción y lógica similar al didSelectRowAtIndexPath:método del delegado UITableView.

Se pueden usar dos enfoques para esto:

a) Delegación: la celda personalizada puede tener una delegatepropiedad y puede definir un protocolo. Cuando se presiona el botón, simplemente realiza sus métodos de delegado en su delegatepropiedad. Pero esta delegatepropiedad debe establecerse para cada celda personalizada cuando se crean. Como alternativa, la celda personalizada puede elegir realizar sus métodos de delegado en la vista de la tabla principal delegatetambién.

b) Centro de notificaciones: las celdas personalizadas pueden definir un nombre de notificación personalizado y publicar esta notificación con la ruta del índice y la información de vista de la tabla principal proporcionada en el userInfoobjeto. No es necesario configurar nada para cada celda, basta con agregar un observador para la notificación de la celda personalizada.

erkanyildiz
fuente
0

Utilizo una solución de esa subclase UIButtony pensé que debería compartirla aquí, códigos en Swift:

class ButtonWithIndexPath : UIButton {
    var indexPath:IndexPath?
}

Luego recuerde actualizar su indexPath en cellForRow(at:)

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let returnCell = tableView.dequeueReusableCell(withIdentifier: "cellWithButton", for: indexPath) as! cellWithButton
    ...
    returnCell.button.indexPath = IndexPath
    returnCell.button.addTarget(self, action:#selector(cellButtonPressed(_:)), for: .touchUpInside)

    return returnCell
}

Entonces, al responder al evento del botón, puede usarlo como

func cellButtonPressed(_ sender:UIButton) {
    if sender is ButtonWithIndexPath {
        let button = sender as! ButtonWithIndexPath
        print(button.indexPath)
    }
}
Ben Ong
fuente