iphone - saltos - numero maximo de filas y columnas en numbers
Mantenga uitableview estático al insertar filas en la parte superior (18)
¿Cómo estás agregando las filas a la mesa?
Si está cambiando la fuente de datos y luego llama a reloadData
, eso puede hacer que la tabla se vuelva a desplazar a la parte superior.
Sin embargo, si usa beginUpdates
, insertRowsAtIndexPaths:withRowAnimation:
endUpdates
métodos endUpdates
, debería poder insertar filas sin tener que llamar a reloadData
, manteniendo así la tabla en su posición original.
No olvide modificar su fuente de datos antes de llamar a endUpdates
o de lo contrario terminará con una excepción de inconsistencia interna.
Tengo una vista de tabla en la que estoy insertando filas en la parte superior.
Mientras hago esto, quiero que la vista actual permanezca completamente quieta, por lo que las filas solo aparecen si se desplaza hacia atrás.
Intenté guardar la posición actual del desplazamiento UIS subyacente y restablecer la posición después de insertar las filas, pero esto da como resultado una vibración, arriba y abajo, aunque termina en el mismo lugar.
¿Hay una buena forma de lograr esto?
Actualización: estoy usando beginUpdate, luego insertRowsAtIndexPath, endUpdates. No hay llamada reloadData.
scrollToRowAtIndexPath salta a la parte superior de la celda actual (guardada antes de agregar filas).
El otro enfoque que probé, que termina exactamente en el ritmo correcto, pero con un judder es.
save tableView currentOffset. (Underlying scrollView method)
Add rows (beginUpdates,insert...,endUpdates)
reloadData ( to force a recalulation of the scrollview size )
Recalculate the correct new offset from the bottom of the scrollview
setContentOffset (Underlying scrollview method)
El problema es que reloadData hace que scrollview / tableview comience a desplazarse brevemente, luego setContentOffset lo devuelve al lugar correcto.
¿Hay alguna manera de hacer que TableView resuelva su nuevo tamaño sin iniciar la visualización?
Envolver todo en un principioAnimation commitmentAnimation tampoco ayuda mucho.
Actualización 2: Esto se puede hacer claramente: consulte la aplicación oficial de Twitter para obtener una cuando realice las actualizaciones.
¿Qué le parece usar scrollToRowAtIndexPath: atScrollPosition: animated :? Debería poder agregar un elemento a su fuente de datos, establecer la fila con el método mencionado anteriormente y volver a cargar la tabla ...
@Decano,
Puedes cambiar tu código así para evitar la animación.
[tableView beginUpdates];
[UIView setAnimationsEnabled:NO];
// ...
[tableView endUpdates];
[tableView setContentOffset:newOffset animated:NO];
[UIView setAnimationsEnabled:YES];
Al final, resolví esto al convertir la tabla actual en un UIImage y luego colocar un UIImageView temporal sobre la tabla mientras se anima.
El siguiente código generará la imagen
// Save the current tableView as an UIImage
CSize pageSize = [[self tableView] frame].size;
UIGraphicsBeginImageContextWithOptions(pageSize, YES, 0.0); // 0.0 means scale appropriate for device ( retina or no )
CGContextRef resizedContext = UIGraphicsGetCurrentContext();
CGPoint offset = [[self tableView] contentOffset];
CGContextTranslateCTM(resizedContext,-(offset.x),-(offset.y));
[[[self tableView ]layer] renderInContext:resizedContext];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Debe realizar un seguimiento de cuánto habrá crecido la tabla al insertar filas y asegúrese de desplazar la vista de tabla a la misma posición exacta.
Basado en la respuesta de Andrey Z, aquí hay un ejemplo en vivo que funciona perfecto para mí ...
int numberOfRowsBeforeUpdate = [controller.tableView numberOfRowsInSection:0];
CGPoint currentOffset = controller.tableView.contentOffset;
if(numberOfRowsBeforeUpdate>0)
{
[controller.tableView reloadData];
int numberOfRowsAfterUpdate = [controller.tableView numberOfRowsInSection:0];
float rowHeight = [controller getTableViewCellHeight]; //custom method in my controller
float offset = (numberOfRowsAfterUpdate-numberOfRowsBeforeUpdate)*rowHeight;
if(offset>0)
{
currentOffset.y = currentOffset.y+offset;
[controller.tableView setContentOffset:currentOffset];
}
}
else
[controller.tableView reloadData];
La forma de @ Dean de usar un caché de imágenes es demasiado hacky y creo que destruye la capacidad de respuesta de la interfaz de usuario.
Una forma adecuada: utilice una subclase UITableView y anule -setContentSize: en la que puede de alguna manera calcular cuánto se empuja la vista de tabla y compensarla configurando contentOffset .
Este es el código de muestra más simple para manejar la situación más simple donde todas las inserciones suceden en la parte superior de la vista de tabla:
@implementation MyTableView
- (void)setContentSize:(CGSize)contentSize {
// I don''t want move the table view during its initial loading of content.
if (!CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
if (contentSize.height > self.contentSize.height) {
CGPoint offset = self.contentOffset;
offset.y += (contentSize.height - self.contentSize.height);
self.contentOffset = offset;
}
}
[super setContentSize:contentSize];
}
@end
Me enfrenté a una situación en la que hay muchas secciones que pueden tener un recuento de filas diferente entre llamadas de carga de datos debido a la agrupación personalizada y las alturas de fila varían. Así que aquí está la solución basada en AndreyZ. Es propiedad contentHeight de UIScrollView antes y después de -reloadData y parece ser más universal.
CGFloat contentHeight = self.tableView.contentSize.height;
CGPoint offset = self.tableView.contentOffset;
[self.tableView reloadData];
offset.y += (self.tableView.contentSize.height - contentHeight);
if (offset.y > [self.tableView contentSize].height)
offset.y = 0;
[self.tableView setContentOffset:offset];
No necesita hacer operaciones tan difíciles, además estas manipulaciones no funcionarían perfectamente. La solución simple es rotar la vista de tabla y luego girar las celdas en ella.
tableView.transform = CGAffineTransformMakeRotation(M_PI);
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.transform = CGAffineTransformMakeRotation(M_PI);
}
Utilice [tableView setScrollIndicatorInsets:UIEdgeInsetsMake(0, 0, 0, 310)]
para establecer la posición relativa al indicador de desplazamiento. Estará en el lado derecho después de la rotación de la vista de tabla.
Quiero agregar una condición adicional. Si su código en iOS11 o más, debe hacerlo a continuación;
En iOS 11, las vistas de tabla usan alturas estimadas de forma predeterminada. Esto significa que contentSize es solo el valor estimado inicialmente. Si necesita usar contentSize, querrá deshabilitar las alturas estimadas estableciendo las 3 propiedades de altura estimadas en cero: tableView.estimatedRowHeight = 0 tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0
Realicé algunas pruebas con un proyecto de muestra de datos centrales y lo dejé quieto mientras se agregaban nuevas células encima de la celda visible superior. Este código necesitaría un ajuste para las tablas con espacio vacío en la pantalla, pero una vez que se llena la pantalla, funciona bien.
static CGPoint delayOffset = {0.0};
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller {
if ( animateChanges )
[self.tableView beginUpdates];
delayOffset = self.tableView.contentOffset; // get the current scroll setting
}
Se agregó esto en los puntos de inserción de la celda. Puede hacer resta de contrapartida para eliminar celdas.
case NSFetchedResultsChangeInsert:
delayOffset.y += self.tableView.rowHeight; // add for each new row
if ( animateChanges )
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
y finalmente
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if ( animateChanges )
{
[self.tableView setContentOffset:delayOffset animated:YES];
[self.tableView endUpdates];
}
else
{
[self.tableView reloadData];
[self.tableView setContentOffset:delayOffset animated:NO];
}
}
Con animateChanges = NO, no pude ver nada moverse cuando se agregaron las celdas.
En las pruebas con animateChanges = YES, el "judder" estaba allí. Parece que la animación de la inserción de la celda no tenía la misma velocidad que el desplazamiento de la tabla animada. Si bien el resultado al final podría terminar con celdas visibles exactamente donde comenzaron, toda la tabla parece moverse 2 o 3 píxeles, luego retroceder.
Si las velocidades de animación pueden ser iguales, puede parecer que se mantiene.
Sin embargo, cuando presioné el botón para agregar filas antes de que terminara la animación anterior, detendría bruscamente la animación y comenzaría la siguiente, haciendo un cambio abrupto de posición.
Realmente no hay necesidad de resumir todas las filas de altura, el nuevo contentSize después de volver a cargar la tabla ya lo está representando. Entonces, todo lo que tiene que hacer es calcular el delta de contentSize height y agregarlo al offset actual.
...
CGSize beforeContentSize = self.tableView.contentSize;
[self.tableView reloadData];
CGSize afterContentSize = self.tableView.contentSize;
CGPoint afterContentOffset = self.tableView.contentOffset;
CGPoint newContentOffset = CGPointMake(afterContentOffset.x, afterContentOffset.y + afterContentSize.height - beforeContentSize.height);
self.tableView.contentOffset = newContentOffset;
...
Respuestas AmitP, versión Swift 3
let beforeContentSize = self.tableView.contentSize
self.tableView.reloadData()
let afterContentSize = self.tableView.contentSize
let afterContentOffset = self.tableView.contentOffset
let newContentOffset = CGPoint(x: afterContentOffset.x, y: afterContentOffset.y + afterContentSize.height - beforeContentSize.height)
self.tableView.contentOffset = newContentOffset
Solo un aviso no parece posible hacer esto si devuelve las alturas estimadas para la tabla vista.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath ;
Si implementa este método y devuelve una altura aproximada, su vista de tabla saltará al volver a cargar, ya que parece utilizar estas alturas al establecer los desplazamientos.
Para que funcione, use una de las respuestas anteriores (utilicé @Mayank Yadav answer), no implemente el método estimadoHeight y guarde en caché las alturas de las celdas (recuerde ajustar la caché cuando inserte celdas adicionales en la parte superior).
Solución simple para deshabilitar animaciones
func addNewRows(indexPaths: [NSIndexPath]) {
let addBlock = { () -> Void in
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .None)
self.tableView.endUpdates()
}
tableView.contentOffset.y >= tableView.height() ? UIView.performWithoutAnimation(addBlock) : addBlock()
}
Tarde en la fiesta, pero esto funciona incluso cuando las celdas tienen alturas dinámicas (también UITableViewAutomaticDimension
como UITableViewAutomaticDimension
), no es necesario iterar sobre las celdas para calcular su tamaño, pero solo funciona cuando los elementos se agregan al inicio de tableView y no hay encabezado, con un poco de matemática, probablemente sea posible adaptar esto a cada situación:
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
self.getMoreMessages()
}
}
private func getMoreMessages(){
var initialOffset = self.tableView.contentOffset.y
self.tableView.reloadData()
//@numberOfCellsAdded: number of items added at top of the table
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: numberOfCellsAdded, inSection: 0), atScrollPosition: .Top, animated: false)
self.tableView.contentOffset.y += initialOffset
}
Todo el mundo ama copiar y pegar ejemplos de código, así que aquí hay una implementación de la respuesta de Andrey Z.
Esto está en mi delegateDidFinishUpdating:(MyDataSourceDelegate*)delegate
método delegateDidFinishUpdating:(MyDataSourceDelegate*)delegate
if (self.contentOffset.y <= 0)
{
[self beginUpdates];
[self insertRowsAtIndexPaths:insertedIndexPaths withRowAnimation:insertAnimation];
[self endUpdates];
}
else
{
CGPoint newContentOffset = self.contentOffset;
[self reloadData];
for (NSIndexPath *indexPath in insertedIndexPaths)
newContentOffset.y += [self.delegate tableView:self heightForRowAtIndexPath:indexPath];
[self setContentOffset:newContentOffset];
NSLog(@"New data at top of table view");
}
El NSLog
en la parte inferior se puede reemplazar con una llamada para mostrar una vista que indica que hay datos nuevos.
tuvo el mismo problema y encontró una solución.
save tableView currentOffset. (Underlying scrollView method)
//Add rows (beginUpdates,insert...,endUpdates) // don''t do this!
reloadData ( to force a recalulation of the scrollview size )
add newly inserted row heights to contentOffset.y here, using tableView:heightForRowAtIndexPath:
setContentOffset (Underlying scrollview method)
Me gusta esto:
- (CGFloat) firstRowHeight
{
return [self tableView:[self tableView] heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}
...
CGPoint offset = [[self tableView] contentOffset];
[self tableView] reloadData];
offset.y += [self firstRowHeight];
if (offset.y > [[self tableView] contentSize].height) {
offset.y = 0;
}
[[self tableView] setContentOffset:offset];
...
funciona perfectamente, sin fallas.
-(void) updateTableWithNewRowCount : (int) rowCount
{
//Save the tableview content offset
CGPoint tableViewOffset = [self.tableView contentOffset];
//Turn of animations for the update block
//to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
[self.tableView beginUpdates];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:SECTION_TO_INSERT];
[rowsInsertIndexPath addObject:tempIndexPath];
heightForNewRows = heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
[self.tableView insertRowsAtIndexPaths:rowsInsertIndexPath withRowAnimation:UITableViewRowAnimationNone];
tableViewOffset.y += heightForNewRows;
[self.tableView endUpdates];
[UIView setAnimationsEnabled:YES];
[self.tableView setContentOffset:tableViewOffset animated:NO];
}
-(int) heightForCellAtIndexPath: (NSIndexPath *) indexPath
{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
int cellHeight = cell.frame.size.height;
return cellHeight;
}
Simplemente pase el recuento de filas de las nuevas filas para insertar en la parte superior.