ios - ¿Hay alguna manera de actualizar la altura de una sola UITableViewCell, sin volver a calcular la altura de cada celda?
objective-c (8)
¿Hay alguna manera? La respuesta es no.
Solo puede usar heightForRowAtIndexPath para esto. Así que todo lo que puede hacer es hacerlo lo más económico posible, por ejemplo manteniendo un NSmutableArray de las alturas de su celda en su modelo de datos.
Tengo una UITableView con algunas secciones diferentes. Una sección contiene celdas que cambiarán de tamaño a medida que el usuario escriba texto en una UITextView. Otra sección contiene celdas que procesan contenido HTML, para lo cual el cálculo de la altura es relativamente costoso.
En este momento, cuando el usuario escribe en UITextView, para obtener la vista de tabla para actualizar el alto de la celda, llamo
[self.tableView beginUpdates];
[self.tableView endUpdates];
Sin embargo, esto hace que la tabla vuelva a calcular el alto de cada celda de la tabla, cuando realmente solo necesito actualizar la única celda en la que se escribió. No solo eso, sino que en lugar de volver a calcular la altura estimada con tableView:estimatedHeightForRowAtIndexPath:
llama a tableView:heightForRowAtIndexPath:
para cada celda, incluso para aquellas que no se muestran.
¿Hay alguna manera de pedirle a la vista de tabla que actualice solo la altura de una sola celda, sin hacer todo este trabajo innecesario?
Actualizar
Todavía estoy buscando una solución a esto. Como se sugirió, he intentado usar reloadRowsAtIndexPaths:
pero no parece que esto funcione. Llamar a reloadRowsAtIndexPaths:
incluso con una única fila todavía se heightForRowAtIndexPath:
para cada fila, aunque cellForRowAtIndexPath:
solo se cellForRowAtIndexPath:
para la fila solicitada. De hecho, parece que cada vez que se inserta, elimina o heightForRowAtIndexPath:
cargar una fila, se llama a heightForRowAtIndexPath:
para cada fila en la celda de la tabla.
También intenté poner el código en willDisplayCell:forRowAtIndexPath:
para calcular la altura justo antes de que aparezca una celda. Para que esto funcione, necesitaría forzar la vista de tabla para volver a solicitar la altura de la fila después de hacer el cálculo. Lamentablemente, se llama a [self.tableView beginUpdates]; [self.tableView endUpdates];
[self.tableView beginUpdates]; [self.tableView endUpdates];
from willDisplayCell:forRowAtIndexPath:
provoca una excepción de índice fuera de límites profundamente en el código interno de UITableView. Supongo que no esperan que hagamos esto.
No puedo evitar sentir que es un error en el SDK que en respuesta a [self.tableView endUpdates]
no llama a estimatedHeightForRowAtIndexPath:
para las celdas que no son visibles, pero todavía estoy tratando de encontrar algún tipo de solución alternativa Cualquier ayuda es apreciada.
Como se indicó, reloadRowsAtIndexPaths:withRowAnimation:
solo hará que la vista de tabla solicite a UITableViewDataSource
una nueva vista de celda, pero no solicitará a UITableViewDelegate
una altura de celda actualizada.
Lamentablemente, la altura solo se actualizará llamando:
[tableView beginUpdates];
[tableView endUpdates];
Incluso sin ningún cambio entre las dos llamadas.
Si su algoritmo para calcular las alturas consume demasiado tiempo, quizás deba almacenar estos valores en caché. Algo como:
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat height = [self cachedHeightForIndexPath:indexPath];
// Not cached ?
if (height < 0)
{
height = [self heightForIndexPath:indexPath];
[self setCachedHeight:height
forIndexPath:indexPath];
}
return height;
}
Y asegúrese de restablecer esas alturas en -1
cuando el contenido cambie o en el momento inicial.
Editar:
Además, si desea retrasar el cálculo de altura tanto como sea posible (hasta que se desplacen) debe intentar implementar esto (iOS 7+ solamente):
@property (nonatomic) CGFloat estimatedRowHeight
Proporcionar una estimación no negativa de la altura de las filas puede mejorar el rendimiento de la carga de la vista de tabla. Si la tabla contiene filas de altura variable, puede ser costoso calcular todas sus alturas cuando se carga la tabla. El uso de la estimación le permite diferir parte del costo del cálculo de la geometría desde el tiempo de carga hasta el tiempo de desplazamiento.
El valor predeterminado es 0, lo que significa que no hay una estimación.
El método heightForRowAtIndexPath:
siempre se llamará, pero aquí hay una solución que yo sugeriría.
Siempre que el usuario escriba en UITextView
, guarde en una variable local el indexPath
de la celda . Luego, cuando se llama a heightForRowAtIndexPath:
, verifique el valor de indexPath
guardado. Si el indexPath
guardado no es nil
, recupere la celda que debe redimensionarse y hágalo. En cuanto a las otras celdas, use sus valores en caché. Si el indexPath
guardado es nil
, ejecute sus líneas regulares de código que en su caso son exigentes.
Así es como recomendaría hacerlo:
Use la tag
de propiedad de UITextView
para realizar un seguimiento de la fila que necesita cambiar de tamaño.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
[textView setDelegate:self];
[textView setTag:indexPath.row];
...
}
Luego, en el método del delegado UITextView
textViewDidChange:
recupera el indexPath
y lo almacena. savedIndexPath
es una variable local.
- (void)textViewDidChange:(UITextView *)textView
{
savedIndexPath = [NSIndexPath indexPathForRow:textView.tag inSection:0];
}
Finalmente, verifique el valor de savedIndexPath
y ejecute lo que se necesita.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (savedIndexPath != nil) {
if (savedIndexPath == indexPath.row) {
savedIndexPath = nil;
// return the new height
}
else {
// return cached value
}
}
else {
// your normal calculating methods...
}
}
¡Espero que esto ayude! Buena suerte.
Este error ha sido reparado en iOS 7.1.
En iOS 7.0, no parece haber forma de evitar este problema. Llamar a [self.tableView endUpdates]
hace que heightForRowAtIndexPath:
para cada celda de la tabla.
Sin embargo, en iOS 7.1, llamar a [self.tableView endUpdates]
hace que heightForRowAtIndexPath:
sea llamado para celdas visibles, y estimatedHeightForRowAtIndexPath:
para llamar a celdas no visibles.
Las alturas de fila variables tienen un impacto muy negativo en el rendimiento de la vista de tabla. Está hablando de contenido web que se muestra en algunas de las celdas. Si no estamos hablando de miles de filas, podría valer la pena considerar la posibilidad de implementar su solución con UIWebView en lugar de con UITableView. Tuvimos una situación similar y fuimos con un UIWebView con marcado HTML generado a medida y funcionó maravillosamente. Como probablemente sepa, tiene un desagradable problema asincrónico cuando tiene una celda dinámica con contenido web:
- Después de configurar el contenido de la celda, debe
- espere hasta que la vista web en la celda termine de mostrar el contenido web,
- luego debes ingresar a UIWebView y, usando JavaScript, preguntar al documento HTML qué tan alto es
- y ENTONCES actualiza la altura de UITableViewCell.
No es divertido en absoluto y muchos saltos y nervios para el usuario.
Si tiene que ir con una UITableView, definitivamente guarde en caché las alturas de fila calculadas. De esta forma, será barato devolverlos en heightForRowAtIndexPath:
En lugar de decirle a UITableView qué hacer, solo haga que su fuente de datos sea rápida.
Terminé descifrando una forma de evitar el problema. Pude calcular previamente la altura del contenido HTML que necesito para representar, e incluir la altura junto con el contenido en la base de datos. De esa manera, aunque todavía estoy obligado a proporcionar la altura para todas las celdas cuando actualizo la altura de cualquier celda, no tengo que hacer ninguna representación costosa de HTML, así que es bastante ágil.
Desafortunadamente, esta solución solo funciona si tienes todo tu contenido HTML por adelantado.
Tuve un problema similar (saltando el desplazamiento de la tabla en cualquier cambio) porque tenía
- (CGFloat) tableView: (UITableView *) tableView estimatedHeightForRowAtIndexPath: (NSIndexPath *) indexPath {return 500; }
comentando que la función completa ayudó.
Utilice el siguiente método UITableView:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
Debe especificar un NSArray de NSIndexPath que desea volver a cargar. Si desea volver a cargar solo una celda, puede suministrar un NSArray que contenga solo un NSIndexPath.
NSIndexPath* rowTobeReloaded = [NSIndexPath indexPathForRow:1 inSection:0];
NSArray* rowsTobeReloaded = [NSArray arrayWithObjects:rowTobeReloaded, nil];
[UITableView reloadRowsAtIndexPaths:rowsTobeReloaded withRowAnimation:UITableViewRowAnimationNone];