Detectar qué UIButton se presionó en un UITableView
-
05-07-2019 - |
Pregunta
Tengo un UITableView
con 5 UITableViewCells
. Cada celda contiene un UIButton
que 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 método buttonPressedAction:
, ¿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 el indexPath
en el 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?
Edición :
Lo he resuelto 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 cola. Se siente muy sucio. Debe haber una mejor manera.
Solución
En la muestra Accesorio 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)
{
...
}
}
Otros consejos
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 consejo texto del enlace
-(void)buttonPressed:(id)sender {
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *clickedButtonPath = [self.tableView indexPathForCell:clickedCell];
...
}
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];
...
}
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...
}
¿Qué tal enviar la información como NSIndexPath
en el UIButton
usando la inyección de tiempo de ejecución.
1) Necesita tiempo de ejecución en la importación
2) agregar constante estática
3) agregue NSIndexPath
a su botón en tiempo de ejecución usando:
(void) setMetaData: (id) objetivo con Object: (id) newObj
4) al presionar el botón obtener metadatos usando:
(id)metaData:(id)target
Disfruta
#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
}
To do (@Vladimir) es Swift:
var buttonPosition = sender.convertPoint(CGPointZero, toView: self.tableView)
var indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)!
Aunque comprobar indexPath! = nil
me da el dedo ... "NSIndexPath no es un subtipo de NSString"
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")
}
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;
Aunque me gusta la forma de etiqueta ... si no quieres usar etiquetas por cualquier razón,
puede crear un miembro NSArray
de botones prefabricados:
NSArray* buttons ;
luego cree esos botones antes de representar el TableView y empújelos dentro de la matriz.
Luego, dentro de la función tableView: cellForRowAtIndexPath:
puede hacer:
UIButton* button = [buttons objectAtIndex:[indexPath row] ] ;
[cell.contentView addSubview:button];
Luego, en la función buttonPressedAction:
, puede hacer
- (void)buttonPressedAction:(id)sender {
UIButton* button = (UIButton*)sender ;
int row = [buttons indexOfObject:button] ;
// Do magic
}
PARA MANEJAR SECCIONES: almacené NSIndexPath en un UITableViewCell personalizado
EN CLKIndexPricesHEADERTableViewCell.xib
EN 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);
}
}
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; //:)
}
Con Swift 4.2 y iOS 12, puede elegir uno de los 5 siguientes ejemplos completos para resolver su problema.
# 1. Usando UIView
' convert (_: to :)
y UITableView
' indexPathForRow (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. Utilizando UIView
' convert (_: to :)
y UITableView
' indexPathForRow (at :)
(alternativa) )
Esta es una alternativa al ejemplo anterior donde pasamos nil
al parámetro target
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
' indexPath (para :)
y delegando el patrón
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. Usando UITableView
' indexPath (para :)
y 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. Uso de UITableViewCell
's accessoriesType
y UITableViewDelegate
' tableView(_:accessoryButtonTappedForRowWith:)
Si su botón es un control de accesorio estándar de UITableViewCell
, cualquier toque en él activará una llamada a UITableViewDelegate
' tableView (_: accesorioButtonTappedForRowWith: )
, que le permite 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)
}
}
También funciona para mí, 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];
...
}
puede 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)
}
¿Me estoy perdiendo algo? ¿No puedes usar el remitente para identificar el botón? El remitente le 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.
// 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];
}
Funcionando 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.
crea una matriz nsmutable y coloca todos los botones en esa matriz usando [array addObject: yourButton];
en el método de presionar un 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
}
}
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 (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];
}
Siempre uso etiquetas.
Debe subclasificar el UITableviewCell
y manejar el botón presionando desde allí.
Es simple; crea una celda personalizada y toma una salida del 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
cambie la identificación en el método anterior a (UIButton *)
Puede obtener el valor de qué botón está siendo tocado haciendo sender.tag.
Subclase el botón para almacenar el valor requerido, tal vez crear 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 extrae los datos. Normalmente almaceno una referencia al objeto real que se representa en la celda de la vista de tabla.
ACTUALIZACIÓN DE SWIFT 2
Aquí se explica cómo averiguar qué botón se tocó + enviar datos a otro ViewController desde el indexPath.row
de ese botón, ¡ya 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 las celdas 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)
}
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);
}
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)
}
Este problema tiene dos partes:
1) Obtener la ruta del índice de UITableViewCell
que contiene UIButton
Hay algunas sugerencias como:
-
Actualizando la
etiqueta
en el métodoUIButton
cellForRowAtIndexPath:
usando el valor defila
de la ruta del índice. Esta no es una buena solución, ya que requiere la actualización continua detag
y no funciona con vistas de tabla con más de una sección. -
Agregar una propiedad
NSIndexPath
a una celda personalizada y actualizarla en lugar de laetiqueta
enUIButton
cellForRowAtIndexPath:
método. Esto resuelve el problema de varias secciones, pero aún no es bueno, ya que requiere una actualización siempre. -
Mantener una referencia débil al
UITableView
padre en la celda personalizada mientras se crea y usar el métodoindexPathForCell:
para obtener la ruta del índice. Parece un poco mejor, no es necesario actualizar nada en el métodocellForRowAtIndexPath:
, pero aún requiere establecer una referencia débil cuando se crea la celda personalizada. -
Uso de la propiedad
superView
de la celda para obtener una referencia al padreUITableView
. No es necesario agregar ninguna propiedad a la celda personalizada, y no es necesario establecer / actualizar nada en la creación / posterior. Pero elsuperView
de la celda depende de los detalles de implementación de iOS. Por lo tanto, no se puede usar directamente.
Pero esto se puede lograr utilizando 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;
Entonces, 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 se puede utilizar para detectar qué UIButton
se presiona.
2) Informar a otras partes sobre el evento de presionar un botón
Después de saber internamente qué UIButton
se presiona en qué celda personalizada con 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 método didSelectRowAtIndexPath:
del delegado UITableView.
Se pueden usar dos enfoques para esto:
a) Delegación: la celda personalizada puede tener una propiedad delegate
y puede definir un protocolo. Cuando se presiona el botón, simplemente realiza sus métodos de delegado en su propiedad delegate
. Pero esta propiedad delegate
debe establecerse para cada celda personalizada cuando se crean. Como alternativa, la celda personalizada puede elegir realizar sus métodos de delegado en su delegate
de la vista de tabla principal tambié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 primaria proporcionada en el objeto userInfo
. No es necesario configurar nada para cada celda, basta con agregar un observador para la notificación de la celda personalizada.
Utilizo una solución que subclase UIButton
y pensé que debería compartirla aquí, códigos en Swift:
class ButtonWithIndexPath : UIButton {
var indexPath:IndexPath?
}
Entonces 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)
}
}