ios - Forma correcta de configurar una etiqueta para todas las celdas en TableView
objective-c uitableview (3)
Estoy usando un botón dentro de tableView
en el que obtengo el indexPath.row
cuando se presiona. Pero solo funciona bien cuando las celdas se pueden mostrar en la pantalla sin scroll
.
Una vez que tableView puede ser desplazable y me desplazo por la vista de tabla, el indexPath.row
devuelto es un valor incorrecto, noté que inicialmente se configuran 20 objetos, por ejemplo, Check
se acaba de imprimir 9 veces, no 20.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
lBtnWithAction = [[UIButton alloc] initWithFrame:CGRectMake(liLight1Xcord + 23, 10, liLight1Width + 5, liLight1Height + 25)];
lBtnWithAction.tag = ROW_BUTTON_ACTION;
lBtnWithAction.titleLabel.font = luiFontCheckmark;
lBtnWithAction.tintColor = [UIColor blackColor];
lBtnWithAction.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[cell.contentView addSubview:lBtnWithAction];
}
else
{
lBtnWithAction = (UIButton *)[cell.contentView viewWithTag:ROW_BUTTON_ACTION];
}
//Set the tag
lBtnWithAction.tag = indexPath.row;
//Add the click event to the button inside a row
[lBtnWithAction addTarget:self action:@selector(rowButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
//This is printed just 9 times (the the number of cells that are initially displayed in the screen with no scroll), when scrolling the other ones are printed
NSLog(@"Check: %li", (long)indexPath.row);
return cell;
}
Para hacer algo con el índice cliqueado:
-(void)rowButtonClicked:(UIButton*)sender
{
NSLog(@"Pressed: %li", (long)sender.tag);
}
Constantes.h
#define ROW_BUTTON_ACTION 9
¿Cuál es la forma correcta de obtener indexPath.row
dentro de rowButtonClicked
o establecer una etiqueta cuando tengo muchas células en mi tableView
?
Mi solución a este tipo de problema no es utilizar una etiqueta de esta manera en absoluto. Es un uso incorrecto completo de las etiquetas (en mi opinión), y es probable que cause problemas en el camino (como ha descubierto), porque las células se reutilizan.
Normalmente, el problema que se resuelve es el siguiente: el usuario interactúa con una parte de la interfaz (por ejemplo, se toca un botón) y ahora queremos saber a qué fila corresponde actualmente esa celda para que podamos responder con respeto. al modelo de datos correspondiente.
La forma en que resuelvo esto en mis aplicaciones es, cuando se toca el botón o lo que sea, y recibo un evento de control o evento delegado de él, para subir la jerarquía de vista desde esa parte de la interfaz (el botón o lo que sea) hasta que llegue a la celda, y luego llama al indexPath(for:)
la vista de indexPath(for:)
, que toma una celda y devuelve la ruta de índice correspondiente. El evento de control o evento de delegado siempre incluye el objeto de interfaz como parámetro, por lo que es fácil ir desde allí a la celda y desde allí a la fila.
Por lo tanto, por ejemplo:
UIView* v = // sender, the interface object
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
UITableViewCell* cell = (UITableViewCell*)v;
NSIndexPath* ip = [self.tableView indexPathForCell:cell];
// and now we know the row (ip.row)
[ NOTA Una posible alternativa sería usar una subclase de celda personalizada en la que tenga una propiedad especial donde almacene la fila en cellForRowAt
. Pero esto me parece completamente innecesario, ya que indexPath(for:)
te da exactamente la misma información. Por otro lado, no hay indexPath(for:)
para un encabezado / pie de página , así que en ese caso utilizo una subclase personalizada que almacena el número de sección, como en este ejemplo (vea la implementación de viewForHeaderInSection
).]
Te estás enfrentando al problema de la reutilización de células.
Cuando crea un botón para la vista, establece una etiqueta, pero luego anula esta etiqueta para establecer el número de fila.
Cuando se reutiliza la celda, porque el número de fila es más largo ROW_BUTTON_ACTION, no restablece la etiqueta al número de fila correcto y las cosas salen mal.
Usar una etiqueta para obtener información de una vista casi siempre es una mala idea y es bastante frágil, como puede ver aquí.
Como Matt ya dijo, caminar por la jerarquía es una mejor idea.
Además, su método no necesita ser escrito de esta manera. Si crea su propia celda personalizada, entonces el código que usa para crear y agregar botones y etiquetas no es necesario, puede hacerlo en un xib, un guión gráfico o incluso en el código de la clase. Además, si usa el método de dequeue que toma la ruta del índice, siempre obtendrá una celda reciclada o una celda recién creada, por lo que no es necesario verificar que la celda devuelta no sea nula.
Estoy de acuerdo con @matt en que este no es un buen uso de las etiquetas, pero estoy un poco en desacuerdo con él sobre la solución. En lugar de subir las supervistas del botón hasta que encuentre una celda, prefiero obtener el origen del botón, convertirlo en coordenadas de vista de tabla y luego pedirle a la vista de tabla el índice de la celda que contiene esas coordenadas.
Ojalá Apple agregara una función indexPathForView(_:)
a UITableView. Es una necesidad común y fácil de implementar. Para ello, aquí hay una extensión simple de UITableView que le permite solicitar una vista de tabla para indexPath de cualquier vista que se encuentre dentro de una de las celdas de tableView.
A continuación se muestra el código de clave para la extensión, tanto en Objective-C como en Swift. Hay un proyecto en funcionamiento en GitHub denominado TableViewExtension-Obj-C que ilustra los usos de la extensión de vista de tabla a continuación.
EDITAR
En Objective-C:
Archivo de cabecera UITableView_indexPathForView.h:
#import <UIKit/UIKit.h>
@interface UIView (indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view;
@end
Archivo UITableView_indexPathForView.m:
#import "UITableView_indexPathForView.h"
@implementation UITableView (UITableView_indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view {
CGPoint origin = view.bounds.origin;
CGPoint viewOrigin = [self convertPoint: origin fromView: view];
return [self indexPathForRowAtPoint: viewOrigin];
}
Y el IBAcción en el botón:
- (void) buttonTapped: (UIButton *) sender {
NSIndexPath *indexPath = [self.tableView indexPathForView: sender];
NSLog(@"Button tapped at indexpPath [%ld-%ld]",
(long)indexPath.section,
(long)indexPath.row);
}
En Swift:
import UIKit
public extension UITableView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let origin = view.bounds.origin
let viewOrigin = self.convert(origin, from: view)
let indexPath = self.indexPathForRow(at: viewOrigin)
return indexPath
}
}
Lo agregué como un archivo "UITableView + indexPathForView" a un proyecto de prueba para asegurarme de que todo estaba correcto. Luego en IBAction para un botón que está dentro de una celda:
func buttonTapped(_ button: UIButton) {
let indexPath = self.tableView.indexPathForView(button)
print("Button tapped at indexPath /(indexPath)")
}
Hice que la extensión funcionara en cualquier UIView, no solo en botones, para que sea más general.
Lo bueno de esta extensión es que puedes colocarla en cualquier proyecto y agrega la nueva función indexPathForView(_:)
a todas tus vistas de tabla sin tener que cambiar tu otro código.