Una celda de vista de tabla no conoce la vista de tabla a la que pertenece y está bien. De hecho, así es como debería ser. Sin embargo, las personas que son nuevas en este concepto a menudo se confunden con él. Por ejemplo, si el usuario toca un botón en una celda de vista de tabla, ¿cómo obtiene la ruta de índice de la celda para poder obtener el modelo correspondiente? En este tutorial, te mostraré cómo no hacer esto, cómo se hace normalmente y cómo hacerlo con estilo y elegancia..
Cuando el usuario toca una celda de vista de tabla, la vista de tabla invoca tableView: didSelectRowAtIndexPath:
del UITableViewDelegate
Protocolo en el delegado de vista de tabla. Este método acepta dos argumentos, la vista de tabla y la ruta de índice de la celda que se seleccionó.
El problema que vamos a abordar en este tutorial, sin embargo, es un poco más complejo. Supongamos que tenemos una vista de tabla con celdas, con cada celda que contiene un botón. Cuando se pulsa el botón, se activa una acción. En la acción, debemos obtener el modelo que corresponde con la posición de la celda en la vista de tabla. En otras palabras, necesitamos conocer la ruta del índice de la celda. ¿Cómo inferimos la ruta del índice de la celda si solo obtenemos una referencia al botón que se pulsó? Ese es el problema que resolveremos en este tutorial..
Crea un nuevo proyecto en Xcode seleccionando el Solicitud de vista única plantilla de la lista de Aplicación para iOS plantillas. Nombra el proyecto Bloques y celulas, conjunto Dispositivos a iPhone, y haga clic Siguiente. Dile a Xcode dónde te gustaría almacenar el proyecto y pulsa Crear.
Abre el Navegador de proyectos a la izquierda, seleccione el proyecto en el Proyecto sección, y establecer la Destino de despliegue a ios 6. Hacemos esto para asegurarnos de que podemos ejecutar la aplicación tanto en iOS 6 como en iOS 7. El motivo de esto se explicará más adelante en este tutorial..
UITableViewCell
SubclaseSeleccionar Nuevo> Archivo ... desde el Expediente menú y elegir Clase objetiva-c de la lista de Toque de cacao plantillas. Nombra la clase TPSButtonCell
y asegúrese de que hereda de UITableViewCell
.
Abra el archivo de encabezado de la clase y declare dos puntos de venta, un UILabel
instancia nombrada titleLabel
y un UIButton
instancia nombrada botón de acción
.
#importar@interface TPSButtonCell: UITableViewCell @property (débil, no atómico) IBOutlet UILabel * titleLabel; @ propiedad (débil, no atómica) IBOutlet UIButton * actionButton; @fin
Abra el archivo de encabezado de la TPSViewController
clase y crear una salida llamada tableView
de tipo UITableView
. los TPSViewController
También necesita adoptar la UITableViewDataSource
y UITableViewDelegate
protocolos.
#importar@interface TPSViewController: UIViewController @ propiedad (débil, no atómica) IBOutlet UITableView * tableView; @fin
También debemos echar un vistazo breve al archivo de implementación del controlador de vista. Abra TPSViewController.m y declare una variable estática de tipo NSString
que usaremos como el identificador de reutilización para las celdas en la vista de tabla.
#importar "TPSViewController.h" @implementation TPSViewController static NSString * CellIdentifier = @ "CellIdentifier"; //… // @final
Abra el storyboard principal del proyecto, Main.Storyboard, y arrastre una vista de tabla a la vista del controlador de vista. Seleccione la vista de tabla y conecte su fuente de datos
y delegar
puntos de venta con la instancia de controlador de vista. Con la vista de tabla aún seleccionada, abra el Inspector de atributos y establece el número de Células prototipo a 1
. los Contenido atributo debe establecerse en Prototipos dinámicos. Ahora debería ver una celda prototipo en la vista de tabla.
Selecciona la celda prototipo y configura su Clase a TPSButtonCell
en el Inspector de identidad. Con la celda aún seleccionada, abre el Inspector de atributos y establecer el Estilo atribuir a Personalizado y el Identificador a Identificador de celda.
Arrastrar un UILabel
instancia de la Biblioteca de objetos a la vista de contenido de la celda y repita este paso para una UIButton
ejemplo. Selecciona la celda, abre el Inspector de conexiones, y conectar el titleLabel
y botón de acción
puntos de venta con sus homólogos en la célula prototipo.
Antes de volver a sumergirnos en el código, necesitamos hacer una conexión más. Seleccione el controlador de vista, abra el Inspector de conexiones Una vez más, y conecte el controlador de vista. tableView
Salida con la vista de tabla en el storyboard. Eso es todo para la interfaz de usuario..
Rellenemos la vista de tabla con algunas películas notables que se lanzaron en 2013. En el TPSViewController
clase, declarar una propiedad de tipo NSArray
y nombrarlo fuente de datos
. La variable de instancia correspondiente contendrá las películas que mostraremos en la vista de tabla. Poblar fuente de datos
con una docena de películas en el controlador de vista viewDidLoad
método.
#import "TPSViewController.h" @interface TPSViewController () @property (strong, nonatomic) NSArray * dataSource; @fin
- (void) viewDidLoad [super viewDidLoad]; // Configura la fuente de datos self.dataSource = @ [@ @ "title": @ "Gravity", @ "year": @ (2013), @ @ "title": @ "12 Years a Slave", @ "year": @ (2013), @ @ "title": @ "Before Midnight", @ "year": @ (2013), @ @ title ": @" American Hustle ", @" year ": @ (2013), @ @" title ": @" Blackfish ", @" year ": @ (2013), @ @ title": @ "Captain Phillips", @ "year": @ (2013), @ @ "title": @ "Nebraska", @ "year": @ (2013), @ @ "title": @ "Rush", @ "year": @ (2013) , @ @ "title": @ "Frozen", @ "year": @ (2013), @ @ "title": @ "Star Trek Into Darkness", @ "year": @ (2013), @ @ "título": @ "The Conjuring", @ "año": @ (2013), @ @ "título": @ "Efectos secundarios", @ "año": @ (2013), @ @ "title": @ "The Attack", @ "year": @ (2013), @ @ "title": @ "The Hobbit", @ "year": @ (2013), @ @ " título ": @" Somos lo que somos ", @" año ": @ (2013), @ @" título ": @" Algo en el aire ", @" año ": @ (2013)];
UITableViewDataSource
ProtocoloLa implementación de la UITableViewDataSource
El protocolo es muy fácil. Solo necesitamos implementar numberOfSectionsInTableView:
, tableView: numberOfRowsInSection:
, y tableView: cellForRowAtIndexPath:
.
- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView return self.dataSource? 1: 0; - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section return self.dataSource? self.dataSource.count: 0; - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * cell = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier para el índice // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Configurar celda de vista de tabla [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", elemento [@ "title"], elemento [@ "year"]]]; [cell.actionButton addTarget: self action: @selector (didTapButton :) forControlEvents: UIControlEventTouchUpInside]; celda de retorno
En tableView: cellForRowAtIndexPath:
, Usamos el mismo identificador que configuramos en el guión gráfico principal., Identificador de celda
, que declaramos anteriormente en el tutorial. Echamos la celda a una instancia de TPSButtonCell
, obtenga el elemento correspondiente del origen de datos y actualice la etiqueta del título de la celda. También agregamos un objetivo y acción para el UIControlEventTouchUpInside
evento del botón.
No olvide agregar una declaración de importación para el TPSButtonCell
clase en la parte superior de TPSViewController.m.
#importar "TPSButtonCell.h"
Para evitar que la aplicación se bloquee cuando se toca un botón, implemente didTapButton:
Como se muestra abajo.
- (void) didTapButton: (id) sender NSLog (@ "% s", __PRETTY_FUNCTION__);
Cree el proyecto y ejecútelo en el simulador de iOS para ver lo que tenemos hasta ahora. Debería ver una lista de películas y al tocar el botón de la derecha, se registra un mensaje en la consola Xcode. Genial. Es hora de la carne del tutorial..
Cuando el usuario toque el botón de la derecha, enviará un mensaje de didTapButton:
al controlador de vista. Casi siempre necesita saber la ruta de índice de la celda de vista de tabla en la que se encuentra el botón. ¿Pero cómo obtiene esa ruta de índice? Como mencioné, hay tres enfoques que puede tomar. Veamos primero cómo no hacerlo..
Eche un vistazo a la implementación de didTapButton:
y tratar de averiguar qué le pasa. ¿Ves el peligro? Deja que te ayude. Primero ejecute la aplicación en iOS 7 y luego en iOS 6. Eche un vistazo a las salidas de Xcode a la consola.
- (void) didTapButton: (id) sender // Buscar celda de vista de tabla UITableViewCell * cell = (UITableViewCell *) [[[sender superview] superview] superview]; // Inferir ruta de índice NSIndexPath * indexPath = [self.tableView indexPathForCell: cell]; // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Log to Console NSLog (@ "% @", elemento [@ "title"]);
El problema con este enfoque es que es propenso a errores. En iOS 7, este enfoque funciona bien. En iOS 6, sin embargo, no funciona. Para que funcione en iOS 6, deberías implementar el método como se muestra a continuación. La jerarquía de vistas de una serie de comunes Vista
subclases, tales como UITableView
, ha cambiado en iOS 7 y el resultado es que el enfoque anterior no produce un resultado consistente.
- (void) didTapButton: (id) sender // Buscar celda de vista de tabla UITableViewCell * cell = (UITableViewCell *) [[sender superview] superview]; // Inferir ruta de índice NSIndexPath * indexPath = [self.tableView indexPathForCell: cell]; // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Log to Console NSLog (@ "% @", elemento [@ "title"]);
¿No podemos simplemente verificar si el dispositivo está ejecutando iOS 7? Esa es una muy buena idea. Sin embargo, ¿qué hará cuando iOS 8 cambie la jerarquía de vista interna de UITableView
una vez mas ¿Vas a aplicar un parche a tu aplicación cada vez que se presenta una versión importante de iOS? ¿Y qué pasa con todos aquellos usuarios que no se actualizan a la última versión (parcheada) de su aplicación? Espero que quede claro que necesitamos una mejor solución..
Un mejor enfoque es inferir la ruta del índice de la celda en la vista de tabla en función de la posición de la remitente
, la UIButton
instancia, en la vista de tabla. Usamos convertPoint: toView:
para lograr esto. Este método convierte el centro del botón del sistema de coordenadas del botón al sistema de coordenadas de la vista de tabla. Entonces se vuelve muy fácil. Nosotros llamamos indexPathForRowAtPoint:
en la vista de mesa y pase pointInSuperview
lo. Esto nos da una ruta de índice que podemos usar para obtener el elemento correcto del origen de datos.
- (void) didTapButton: (id) sender // Cast Sender to UIButton UIButton * button = (UIButton *) sender; // Buscar punto en Superview CGPoint pointInSuperview = [button.superview convertPoint: button.center toView: self.tableView]; // Inferir ruta de índice NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint: pointInSuperview]; // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Log to Console NSLog (@ "% @", elemento [@ "title"]);
Este enfoque puede parecer engorroso, pero en realidad no lo es. Es un enfoque que no se ve afectado por los cambios en la jerarquía de vistas de UITableView
y se puede utilizar en muchos escenarios, incluso en vistas de colección.
Hay una solución más para resolver el problema y requiere un poco más de trabajo. El resultado, sin embargo, es una exhibición del moderno Objective-C. Comience por revisar el archivo de encabezado de la TPSButtonCell
y declarar un método público llamado setDidTapButtonBlock:
que acepta un bloque.
#importar@interface TPSButtonCell: UITableViewCell @property (débil, no atómico) IBOutlet UILabel * titleLabel; @ propiedad (débil, no atómica) IBOutlet UIButton * actionButton; - (void) setDidTapButtonBlock: (void (^) (remitente de identificación)) didTapButtonBlock; @fin
En el archivo de implementación de TPSButtonCell
crear una propiedad privada llamada didTapButtonBlock
Como se muestra abajo. Tenga en cuenta que la propiedad atribuida se establece en dupdo
, porque los bloques deben copiarse para realizar un seguimiento de su estado capturado fuera del alcance original.
#importar "TPSButtonCell.h" @interface TPSButtonCell () @property (copy, nonatomic) void (^ didTapButtonBlock) (identificador del remitente); @fin
En lugar de agregar un objetivo y una acción para el UIControlEventTouchUpInside
evento en el controlador de vista tableView: cellForRowAtIndexPath:
, Añadimos un objetivo y acción en. awakeFromNib
en el TPSButtonCell
clase en si.
- (void) awakeFromNib [super awakeFromNib]; [self.actionButton addTarget: self action: @selector (didTapButton :) forControlEvents: UIControlEventTouchUpInside];
La implementación de didTapButton:
es trivial.
- (void) didTapButton: (id) sender if (self.didTapButtonBlock) self.didTapButtonBlock (sender);
Esto puede parecer mucho trabajo para un simple botón, pero mantenga sus caballos hasta que lo hayamos refaccionado tableView: cellForRowAtIndexPath:
en el TPSViewController
clase. En lugar de agregar un objetivo y una acción al botón de la celda, configuramos la celda didTapButtonBlock
. Obtener una referencia al elemento correspondiente de la fuente de datos se vuelve muy, muy fácil. Esta solución es, con mucho, la solución más elegante a este problema..
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * cell = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath indexPath: indexPath // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Configurar celda de vista de tabla [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", elemento [@ "title"], elemento [@ "year"]]]; [cell setDidTapButtonBlock: ^ (remitente de identificación) NSLog (@ "% @", elemento [@ "title"]); ]; celda de retorno
A pesar de que el concepto de bloques ha existido durante décadas, los desarrolladores de Cocoa han tenido que esperar hasta 2011. Los bloques pueden hacer que los problemas complejos sean más fáciles de resolver y simplificar los códigos complejos. Desde la introducción de los bloques, Apple ha comenzado a utilizarlos extensivamente en sus propias API, por lo que lo invito a seguir el ejemplo de Apple aprovechando los bloques en sus propios proyectos..