UITableViewCell expande al hacer clic

Digamos que tenemos una UITableViewCell personalizada

Entonces cada vez que hago clic en el botón personalizado en la celda … debe expandirse hasta cierto punto (puede decir 40 más de altura …) y cuando vuelvo a hacer clic en el mismo botón personalizado, debe colapsar a la altura anterior.

Desarrollador, por favor guíame … ¿cómo puedo lograr esta tarea?

Implemente heightForRowAtIndexPath para calcular la altura correcta. Luego, en el código de su botón, fuerce la tabla para reevaluar la altura de cada celda con beginUpdates plus endUpdates:

[self.tableView beginUpdates]; [self.tableView endUpdates]; 

Los cambios en las alturas de las celdas de tabla se calcularán automáticamente con heightForRowAtIndexPath y los cambios también se animarán.

De hecho, en lugar de un botón en su celda que hace esto, incluso puede hacer que la selección de la celda lo haga en didSelectRowAtIndexPath .

No voy a decir nada aquí para contradecir la respuesta aceptada, ya que es perfectamente correcta. Sin embargo, voy a entrar en más detalles sobre cómo lograr esto. Si no quiere leer todo esto y está más interesado en jugar con el código fuente en un proyecto en funcionamiento, he cargado un proyecto de ejemplo a GitHub .

La idea básica es tener una condición dentro del método -tableView: heightForRowAtIndexPath: eso determina si la celda actual debe expandirse o no. Esto se activará al invocar las actualizaciones de inicio / final en la tabla desde dentro de -tableView: didSelectRowAtIndexPath: en este ejemplo, mostraré cómo hacer una vista de tabla que permita expandir una celda a la vez.

Lo primero que deberá hacer es declarar una referencia a un objeto NSIndexPath . Puedes hacer esto como quieras, pero te recomiendo usar una statement de propiedad como esta:

 @property (strong, nonatomic) NSIndexPath *expandedIndexPath; 

NOTA: No necesita crear esta ruta de índice dentro de viewDidLoad, o cualquier otro método similar. El hecho de que el índice sea inicialmente nulo solo significará que la tabla inicialmente no tendrá una fila expandida. Si prefiere que la tabla comience con una fila de su elección expandida, puede agregar algo como esto a su método viewDidLoad:

 NSInteger row = 1; NSInteger section = 2; self.expandedIndexPath = [NSIndexPath indexPathForRow:row inSection:section]; 

El siguiente paso es dirigirse a su método -tableView: didSelectRowAtIndexPath: para agregar la lógica para alterar el índice de celda expandida según la selección de usuarios. La idea aquí es verificar la ruta de índice que acaba de seleccionarse contra la ruta de índice almacenada dentro de la variable expandedIndexPath . Si los dos coinciden, entonces sabemos que el usuario está intentando anular la selección de la celda expandida, en cuyo caso, establecemos la variable como nula. De lo contrario, establecemos la variable expandedIndexPath en el índice que se acaba de seleccionar. Todo esto se hace entre llamadas a beginUpdates / endUpdates, para permitir que la vista de tabla maneje automáticamente la animación de transición.

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView beginUpdates]; // tell the table you're about to start making changes // If the index path of the currently expanded cell is the same as the index that // has just been tapped set the expanded index to nil so that there aren't any // expanded cells, otherwise, set the expanded index to the index that has just // been selected. if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) { self.expandedIndexPath = nil; } else { self.expandedIndexPath = indexPath; } [tableView endUpdates]; // tell the table you're done making your changes } 

Luego, el último paso es en otro método -tableView: heightForRowAtIndexPath: Se llamará a este método después de haber activado beginUpdates una vez para cada ruta de índice que la tabla determine que necesita actualizarse. Aquí es donde comparará el expandedIndexPath con la ruta de índice que se está reevaluando actualmente.

Si las dos rutas de índice son iguales, entonces esta es la celda que desea expandir, de lo contrario su altura debería ser normal. Usé los valores 100 y 44, pero puede usar lo que mejor se adapte a sus necesidades.

 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // Compares the index path for the current cell to the index path stored in the expanded // index path variable. If the two match, return a height of 100 points, otherwise return // a height of 44 points. if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) { return 100.0; // Expanded height } return 44.0; // Normal height } 

He creado una biblioteca de código abierto para esto. ¡Simplemente implementas el colapso y expandes delegates en tu código y voilà ! también puedes realizar dibujos y animaciones. mira esto .

enter image description here

He creado un componente reutilizable que hará exactamente lo que estás diciendo. Es bastante fácil de usar, y hay un proyecto de demostración.

GCRetrableSectionController en GitHub.

En lugar de usar [tableView beginUpdates] y [tableView endUpdates] , estoy usando el [tableView reloadRowsAtIndexPath:... withRowAnimation:...] dentro del método didSelectRowAtIndexPath .

Prefiero esto, porque tuve algunos problemas con elementos que deberían mostrarse, cuando UITableViewCell mi UITableViewCell , cuando utilicé los métodos de actualización de inicio y finalización. Otro punto es que puedes elegir entre algunas animaciones como: Arriba, Abajo, Izquierda, Derecha …

Utilicé el código fuente de Gcamp e hice mi propia versión.

1) En un método loadView, inicialice una matriz mutable donde guardará estados expandidos o no expandidos de sus secciones. Es fundamental guardar los estados expandidos en una matriz separada, que no se destruye mientras que la vista de tabla se desplaza (por ejemplo, si la almacena en un encabezadoView se volverá a dibujar y olvidará el tiempo en que se expandió o no). En mi caso, es _sectionStatuses array.

 - (void)loadView { // At the beginning all sections are expanded _sectionStates = [NSMutableArray arrayWithCapacity:self.tableView.numberOfSections]; for (int i = 0; i < self.tableView.numberOfSections; i++) { _sectionStates[i] = [NSNumber numberWithBool:YES]; } } 

2) Cree un headerView personalizado para una sección con un botón para expandir. Delegue una acción desde un botón en su headerView a su TableViewController usando un patrón de delegación. Puede encontrar imágenes adecuadas en el código fuente de Gcamp.

3) Crea una acción para eliminar o agregar filas. Aquí _foldersArray es mi estructura, que contiene todos los datos. El headerView de mi sección - MCExpandableAccountHeaderView sabe que es su propio número de sección - Lo transfiero allí cuando creo vistas de encabezado para cada sección. Es fundamental transferirlo a este método, ya que debe saber qué sección ahora está expandida o estirada.

 - (void)expandClicked:(MCAccountHeaderView *)sender { MCExpandableAccountHeaderView *expandableAccountHeaderView = (MCExpandableAccountHeaderView*)sender; // Finding a section, where a button was tapped NSInteger section = expandableAccountHeaderView.section; // Number of rows, that must be in a section when it is expanded NSUInteger contentCount = [_foldersArray[section - 1][@"folders"] count]; // Change a saved status of a section BOOL expanded = [_sectionStates[section] boolValue]; expanded = ! expanded; expandableAccountHeaderView.expanded = expanded; _sectionStates[section] = [NSNumber numberWithBool:expanded]; // Animation in a table [self.tableView beginUpdates]; NSMutableArray* modifiedIndexPaths = [[NSMutableArray alloc] init]; for (NSUInteger i = 0; i < contentCount; i++) { NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:section]; [modifiedIndexPaths addObject:indexPath]; } if (expandableAccountHeaderView.expanded) [self.tableView insertRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade]; else [self.tableView deleteRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade]; [self.tableView endUpdates]; // Scroll to the top of current expanded section if (expandableAccountHeaderView.expanded) [self.tableView scrollToRowAtIndexPath:INDEX_PATH(0, section) atScrollPosition:UITableViewScrollPositionTop animated:YES]; } 

4) También es importante devolver el número correcto o las filas en una sección, según el tiempo en que se expanda o no.

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { BOOL expanded = [_sectionStates[section] boolValue]; return expanded ? [_foldersArray[section - 1][@"folders"] count] : 0; } 

Esta es la respuesta de Mick, pero para Swift 4. (IndexPath reemplaza NSIndexPath, que viene con un IndexPath vacío, ya que nil podría bloquear Swift. Además, puede comparar dos instancias de IndexPath usando == )

Declare la propiedad expandedIndexPath.

 var expandedIndexPath = IndexPath() 

Parte viewDidLoad opcional.

 expandedIndexPath = IndexPath(row: 1, section: 2) 

Luego la parte didSelectRow.

 override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.beginUpdates() if indexPath == expandedIndexPath { expandedIndexPath = IndexPath() } else { expandedIndexPath = indexPath } tableView.endUpdates() } 

Luego, la parte heightForRow.

 override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath == expandedIndexPath { return 100 } return 44 } 
 initialize iSelectedIndex = -1; and declare UITableView *urTableView; - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return 10; //Section count } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 3; //row count } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if(cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } [cell.textLabel setText:[NSString stringWithFormat:@"sec:%d,row:%d",indexPath.section,indexPath.row]]; return cell; } - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ // adding a label with the tap gesture to the header in each section headerLabel = [[UILabel alloc]init]; headerLabel.tag = section; headerLabel.userInteractionEnabled = YES; headerLabel.backgroundColor = [UIColor greenColor]; headerLabel.text = [NSString stringWithFormat:@"Header No.%d",section]; headerLabel.frame = CGRectMake(0, 0, tableView.tableHeaderView.frame.size.width, tableView.tableHeaderView.frame.size.height); UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(gestureTapped:)]; [headerLabel addGestureRecognizer:tapGesture]; return headerLabel; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ return 50.0; //adjust the height as you need } - (void)gestureTapped:(UITapGestureRecognizer *)sender{ UIView *theSuperview = self.view; // whatever view contains CGPoint touchPointInSuperview = [sender locationInView:theSuperview]; UIView *touchedView = [theSuperview hitTest:touchPointInSuperview withEvent:nil]; if([touchedView isKindOfClass:[UILabel class]]) { if (iSelectedIndex != touchedView.tag) { //if new header is selected , need to expand iSelectedIndex = touchedView.tag; }else{ // if the header is already expanded , need to collapse iSelectedIndex = -1; } [urTableView beginUpdates]; [urTableView endUpdates]; } } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // Show or hide cell float height = 0.0; if (indexPath.section == iSelectedIndex) { height = 44.0; // Show the cell - adjust the height as you need } return height; } 

Para agregar a la respuesta de 0x7fffffff , descubrí que necesitaba una condición adicional en la statement if dentro de didSelectRowAtIndexPath , por lo tanto:

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView beginUpdates]; if (self.expandedIndexPath && [indexPath compare:self.expandedIndexPath] == NSOrderedSame) { self.expandedIndexPath = nil; } else { self.expandedIndexPath = indexPath; } [tableView endUpdates]; }