¿Se puede animar un cambio de altura en un UITableViewCell cuando se selecciona?

393

Estoy usando una UITableViewaplicación en mi iPhone y tengo una lista de personas que pertenecen a un grupo. Me gustaría que cuando el usuario haga clic en una persona en particular (seleccionando así la celda), la celda crezca en altura para mostrar varios controles de interfaz de usuario para editar las propiedades de esa persona.

es posible?

Mark Amery
fuente

Respuestas:

879

Encontré una solución REALMENTE SIMPLE para esto como efecto secundario de un UITableViewtrabajo en el que estaba trabajando .....

Almacene la altura de la celda en una variable que informa la altura original normalmente a través de tableView: heightForRowAtIndexPath:, luego, cuando desee animar un cambio de altura, simplemente cambie el valor de la variable y llame a esto ...

[tableView beginUpdates];
[tableView endUpdates];

Descubrirá que no se recarga completamente, pero es suficiente para UITableViewsaber que tiene que volver a dibujar las celdas, tomando el nuevo valor de altura para la celda ... ¿y adivina qué? ANIMA el cambio para ti. Dulce.

Tengo una explicación más detallada y ejemplos de código completo en mi blog ... Animar UITableView Cell Height Change

Simon Lee
fuente
66
Esto es brillante. Pero, ¿hay alguna forma de controlar la velocidad de animación de la tabla?
Josh Kahane el
77
Esto funciona, pero si hago una celda más grande, digamos de 44 a 74 y luego la hago más pequeña nuevamente a 44, la línea de separación actúa de manera totalmente extraña. ¿Puede alguien confirmar esto?
plaetzchen
46
Esta es una solución extraña, pero es lo que Apple recomienda también en la sesión "Mastering Table View" de WWDC 2010. Voy a presentar un informe de error al agregar esto a la documentación porque acabo de pasar aproximadamente 2 horas investigando.
bpapa
55
He probado esta solución y solo funciona a veces, cuando presionas una celda hay un 50% de probabilidad de funcionar. Alguien tiene el mismo error? ¿Es porque iOS7?
Joan Cardona
77
Ahora también está escrito en la documentación oficial: You can also use this method followed by the endUpdates method to animate the change in the row heights without reloading the cell. developer.apple.com/library/ios/documentation/UIKit/Reference/…
Jaroslav
63

Me gusta la respuesta de Simon Lee. En realidad no probé ese método, pero parece que cambiaría el tamaño de todas las celdas de la lista. Esperaba un cambio de solo la celda que está intervenida. Lo hice como Simon pero con una pequeña diferencia. Esto cambiará el aspecto de una celda cuando se seleccione. Y lo hace animado. Solo otra forma de hacerlo.

Cree un int para contener un valor para el índice de celda seleccionado actual:

int currentSelection;

Entonces:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    int row = [indexPath row];
    selectedNumber = row;
    [tableView beginUpdates];
    [tableView endUpdates];
}

Entonces:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    if ([indexPath row] == currentSelection) {
        return  80;
    }
    else return 40;


}

Estoy seguro de que puede hacer cambios similares en tableView: cellForRowAtIndexPath: para cambiar el tipo de celda o incluso cargar un archivo xib para la celda.

De esta forma, la selección actual comenzará en 0. Debería hacer ajustes si no desea que la primera celda de la lista (en el índice 0) se vea seleccionada de forma predeterminada.

Mark A.
fuente
2
Verifique el código adjunto a mi publicación, hice exactamente esto, doblé la altura de una celda cuando se seleccionó. :)
Simon Lee
8
"En realidad no probé ese método, pero parece que cambiaría el tamaño de todas las celdas de la lista", entonces no lo miraste demasiado.
Jamie
13
La selección actual ya está almacenada en tableView.indexPathForSelectedRow.
Nick
22

Agregue una propiedad para realizar un seguimiento de la celda seleccionada

@property (nonatomic) int currentSelection;

Ajústelo a un valor centinela en (por ejemplo) viewDidLoad, para asegurarse de que UITableViewcomience en la posición 'normal'

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    //sentinel
    self.currentSelection = -1;
}

En heightForRowAtIndexPathpuede establecer la altura que desea para la celda seleccionada

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    int rowHeight;
    if ([indexPath row] == self.currentSelection) {
        rowHeight = self.newCellHeight;
    } else rowHeight = 57.0f;
    return rowHeight;
}

En didSelectRowAtIndexPathguardar la selección actual y guardar una altura dinámica, si es necesario

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        // do things with your cell here

        // set selection
        self.currentSelection = indexPath.row;
        // save height for full text label
        self.newCellHeight = cell.titleLbl.frame.size.height + cell.descriptionLbl.frame.size.height + 10;

        // animate
        [tableView beginUpdates];
        [tableView endUpdates];
    }
}

En didDeselectRowAtIndexPathestablecer el índice de selección de nuevo al valor centinela y animar la celda a su forma normal

- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {       
        // do things with your cell here

        // sentinel
        self.currentSelection = -1;

        // animate
        [tableView beginUpdates];
        [tableView endUpdates];
    }
}
Alegría
fuente
¡Gracias, gracias, gracias! Agregué un poco de código para que la celda sea capaz de alternar. Agregué el código a continuación.
Septronic
14

reloadData no es bueno porque no hay animación ...

Esto es lo que estoy intentando actualmente:

NSArray* paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];

Casi funciona bien. Casi. Estoy aumentando la altura de la celda, y a veces hay un pequeño "hipo" en la vista de tabla a medida que se reemplaza la celda, como si se conservara alguna posición de desplazamiento en la vista de tabla, la nueva celda (que es la primera celda en la tabla) termina con su desplazamiento demasiado alto, y la vista de desplazamiento rebota para reposicionarlo.

Lawrence
fuente
Personalmente, descubrí que usar este método pero con UITableViewRowAnimationNone proporciona un resultado más suave pero aún no perfecto.
Ron Srebro
11

No sé qué es todo esto sobre llamar a beginUpdates / endUpdates en sucesión, solo puedes usarlo -[UITableView reloadRowsAtIndexPaths:withAnimation:]. Aquí hay un proyecto de ejemplo .

axiixc
fuente
Excelente, esto no estira mis vistas de texto en mi celda de diseño automático. Pero tiene que tener un parpadeo en la animación porque la opción de animación Ninguno parece tener fallas al actualizar el tamaño de la celda.
h3dkandi
10

Resolví con reloadRowsAtIndexPaths.

didSelectRowAtIndexPathGuardo en el indexPath de la celda seleccionada y llamoreloadRowsAtIndexPaths al final (puede enviar NSMutableArray para obtener una lista de los elementos que desea recargar).

En heightForRowAtIndexPathpuede verificar si indexPath está en la lista o no de las celdas expandIndexPath y enviar la altura.

Puede consultar este ejemplo básico: https://github.com/ferminhg/iOS-Examples/tree/master/iOS-UITableView-Cell-Height-Change/celdascambiadetam Es una solución simple.

agrego una especie de código si te ayudo

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 20;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath*)indexPath
{
    if ([indexPath isEqual:_expandIndexPath])
        return 80;

    return 40;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Celda";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    [cell.textLabel setText:@"wopwop"];

    return cell;
}

#pragma mark -
#pragma mark Tableview Delegate Methods

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSMutableArray *modifiedRows = [NSMutableArray array];
    // Deselect cell
    [tableView deselectRowAtIndexPath:indexPath animated:TRUE];
    _expandIndexPath = indexPath;
    [modifiedRows addObject:indexPath];

    // This will animate updating the row sizes
    [tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
}
fermín
fuente
3

Pruebe esto para expandir la fila indexwise:

@property (nonatomic) NSIndexPath *expandIndexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
if ([indexPath isEqual:self.expandedIndexPath])
    return 100;

return 44;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *modifiedRows = [NSMutableArray array];
if ([indexPath isEqual:self.expandIndexPath]) {
    [modifiedRows addObject:self.expandIndexPath];
    self.expandIndexPath = nil;
} else {
    if (self.expandedIndexPath)
        [modifiedRows addObject:self.expandIndexPath];

    self.expandIndexPath = indexPath;
    [modifiedRows addObject:indexPath];
}

// This will animate updating the row sizes
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];

// Preserve the deselection animation (if desired)
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ViewControllerCellReuseIdentifier];
    cell.textLabel.text = [NSString stringWithFormat:@"I'm cell %ld:%ld", (long)indexPath.section, (long)indexPath.row];

return cell;
}
Nagarjun
fuente
3
BOOL flag;

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    flag = !flag;
    [tableView beginUpdates];
    [tableView reloadRowsAtIndexPaths:@[indexPath] 
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView endUpdates];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES == flag ? 20 : 40;
}
Solodyashkin romano
fuente
2

solo una nota para alguien como yo buscando agregar "Más detalles" en la celda personalizada.

[tableView beginUpdates];
[tableView endUpdates];

Hizo un trabajo excelente, pero no olvide "recortar" la vista de celda. Desde Interface Builder, seleccione su Celda -> Vista de contenido -> desde el Inspector de propiedades seleccione " Subvista de clip "

Anthony Marchenko
fuente
2

Aquí hay una versión más corta de la respuesta de Simons para Swift 3. También permite alternar la selección de la celda

var cellIsSelected: IndexPath?


  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    cellIsSelected = cellIsSelected == indexPath ? nil : indexPath
    tableView.beginUpdates()
    tableView.endUpdates()
  }


  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if cellIsSelected == indexPath {
      return 250
    }
    return 65
  }
aBikis
fuente
2

Swift 4 y superior

agregue el código a continuación en el método de delegado de fila didselect de su vista de tabla

tableView.beginUpdates()
tableView.setNeedsLayout()
tableView.endUpdates()
Midhun p
fuente
1

Versión rápida de la respuesta de Simon Lee.

// MARK: - Variables 
  var isCcBccSelected = false // To toggle Bcc.



    // MARK: UITableViewDelegate
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {

    // Hide the Bcc Text Field , until CC gets focused in didSelectRowAtIndexPath()
    if self.cellTypes[indexPath.row] == CellType.Bcc {
        if (isCcBccSelected) {
            return 44
        } else {
            return 0
        }
    }

    return 44.0
}

Luego en didSelectRowAtIndexPath ()

  func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    self.tableView.deselectRowAtIndexPath(indexPath, animated: true)

    // To Get the Focus of CC, so that we can expand Bcc
    if self.cellTypes[indexPath.row] == CellType.Cc {

        if let cell = tableView.cellForRowAtIndexPath(indexPath) as? RecipientTableViewCell {

            if cell.tag == 1 {
                cell.recipientTypeLabel.text = "Cc:"
                cell.recipientTextField.userInteractionEnabled = true
                cell.recipientTextField.becomeFirstResponder()

                isCcBccSelected = true

                tableView.beginUpdates()
                tableView.endUpdates()
            }
        }
    }
}
ioopl
fuente
1

Si es posible.

UITableView tiene un método delegado didSelectRowAtIndexPath

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [UIView animateWithDuration:.6
                          delay:0
         usingSpringWithDamping:UIViewAnimationOptionBeginFromCurrentState
          initialSpringVelocity:0
                        options:UIViewAnimationOptionBeginFromCurrentState animations:^{

                            cellindex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
                            NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
                            [violatedTableView beginUpdates];
                            [violatedTableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationAutomatic];
                            [violatedTableView endUpdates];
                        }
                     completion:^(BOOL finished) {
    }];
}

Pero en su caso, si el usuario se desplaza y selecciona una celda diferente, entonces necesita tener la última celda seleccionada para reducir y expandir las reloadRowsAtIndexPaths:llamadas de celda actualmente seleccionadas , heightForRowAtIndexPath:así que maneje en consecuencia.

Koushik
fuente
0

Aquí está mi código de UITableViewsubclase personalizada , que se expande UITextViewen la celda de la tabla, sin recargar (y perder el foco del teclado):

- (void)textViewDidChange:(UITextView *)textView {
    CGFloat textHeight = [textView sizeThatFits:CGSizeMake(self.width, MAXFLOAT)].height;
    // Check, if text height changed
    if (self.previousTextHeight != textHeight && self.previousTextHeight > 0) {
        [self beginUpdates];

        // Calculate difference in height
        CGFloat difference = textHeight - self.previousTextHeight;

        // Update currently editing cell's height
        CGRect editingCellFrame = self.editingCell.frame;
        editingCellFrame.size.height += difference;
        self.editingCell.frame = editingCellFrame;

        // Update UITableView contentSize
        self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height + difference);

        // Scroll to bottom if cell is at the end of the table
        if (self.editingNoteInEndOfTable) {
            self.contentOffset = CGPointMake(self.contentOffset.x, self.contentOffset.y + difference);
        } else {
            // Update all next to editing cells
            NSInteger editingCellIndex = [self.visibleCells indexOfObject:self.editingCell];
            for (NSInteger i = editingCellIndex; i < self.visibleCells.count; i++) {
                UITableViewCell *cell = self.visibleCells[i];
                CGRect cellFrame = cell.frame;
                cellFrame.origin.y += difference;
                cell.frame = cellFrame;
            }
        }
        [self endUpdates];
    }
    self.previousTextHeight = textHeight;
}
Vitaliy Gozhenko
fuente
0

Usé la increíble respuesta de @ Joy, y funcionó perfectamente con ios 8.4 y XCode 7.1.1.

En caso de que desee hacer que su celda sea capaz de alternar, cambié -tableViewDidSelect a lo siguiente:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//This is the bit I changed, so that if tapped once on the cell, 
//cell is expanded. If tapped again on the same cell, 
//cell is collapsed. 
    if (self.currentSelection==indexPath.row) {
        self.currentSelection = -1;
    }else{
        self.currentSelection = indexPath.row;
    }
        // animate
        [tableView beginUpdates];
        [tableView endUpdates];

}

Espero que todo esto te haya ayudado.

Septronic
fuente
0

Verifique este método después de iOS 7 y posterior.

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return UITableViewAutomaticDimension;
}

Se han realizado mejoras en esto en iOS 8. Podemos configurarlo como propiedad de la vista de tabla en sí.

Govind
fuente
0

Versión rápida de la respuesta de Simon Lee :

tableView.beginUpdates()
tableView.endUpdates()

Tenga en cuenta que debe modificar las propiedades de altura ANTES endUpdates() .

Tamás Sengel
fuente
0

Entradas -

tableView.beginUpdates () tableView.endUpdates () estas funciones no llamarán

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

Pero, si lo hace, tableView.reloadRows (en: [selectedIndexPath! Como IndexPath], con: .none)

Llamará al func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {} esta función.

sRoy
fuente
-1

Acabo de resolver este problema con un pequeño truco:

static int s_CellHeight = 30;
static int s_CellHeightEditing = 60;

- (void)onTimer {
    cellHeight++;
    [tableView reloadData];
    if (cellHeight < s_CellHeightEditing)
        heightAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(onTimer) userInfo:nil repeats:NO] retain];
}

- (CGFloat)tableView:(UITableView *)_tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
        if (isInEdit) {
            return cellHeight;
        }
        cellHeight = s_CellHeight;
        return s_CellHeight;
}

Cuando necesito expandir la altura de la celda, configuro isInEdit = YESy llamo al método [self onTimer]y anima el crecimiento de la celda hasta que alcanza el valor s_CellHeightEditing :-)

Dzamir
fuente
En el simulador funciona muy bien, pero en el iPhone el hardware es lento. Con un retraso de 0,05 contador de tiempo y con un aumento cellHeight de 5 unidades, que es mucho mejor, pero nada como CoreAnimation
Dzamir
1
Confirmar, antes de publicar ... la próxima vez.
ajay_nasa
-2

Obtenga la ruta índice de la fila seleccionada. Recargar la mesa. En el método heightForRowAtIndexPath de UITableViewDelegate, establezca el alto de la fila seleccionada en un alto diferente y para los demás devuelva el alto de fila normal

perdido en el transito
fuente
2
-1, no funciona. Los [table reloadData]resultados de llamadas en el cambio de altura ocurren instantáneamente, en lugar de animar.
Mark Amery