Tener un reloadData para un animado UITableView al cambiar

183

Tengo un UITableView que tiene dos modos. Cuando cambiamos entre los modos, tengo un número diferente de secciones y celdas por sección. Idealmente, sería una animación genial cuando la tabla crezca o se reduzca.

Aquí está el código que probé, pero no hace nada:

CGContextRef context = UIGraphicsGetCurrentContext(); 
[UIView beginAnimations:nil context:context]; 
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; 
[UIView setAnimationDuration:0.5]; 

[self.tableView reloadData];
[UIView commitAnimations];

¿Alguna idea sobre cómo podría hacer esto?

Vertexwahn
fuente

Respuestas:

400

En realidad, es muy simple:

[_tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];

De la documentación :

Al llamar a este método, la vista de tabla solicita a su fuente de datos nuevas celdas para las secciones especificadas. La vista de tabla anima la inserción de nuevas celdas mientras anima las viejas celdas.

dmarnel
fuente
13
¡Y simplemente la mejor respuesta en esta página!
Matthias D
41
Esto no es del todo correcto, ya que solo volverá a cargar la primera sección. Si su tabla tiene varias secciones, esto no funcionará
Nosrettap
44
Es posible que obtenga una excepción si no ha cambiado ningún dato. Vea mi respuesta
Matej
8
@Nosrettap: si desea tener más o todas las secciones de su recarga tableView, todo lo que tiene que hacer es extender su NSIndexSet con todos los índices de sección, que también debe actualizarse
JonEasy
Versión rápida: Sin _tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Fade)embargo, notablemente, solo actualiza la Sección 0, que es la primera sección predeterminada.
kbpontius
264

Es posible que desee utilizar:

C objetivo

[UIView transitionWithView: self.tableView
                  duration: 0.35f
                   options: UIViewAnimationOptionTransitionCrossDissolve
                animations: ^(void)
 {
      [self.tableView reloadData];
 }
                completion: nil];

Rápido

UIView.transitionWithView(tableView,
                          duration: 0.35,
                          options: .TransitionCrossDissolve,
                          animations:
{ () -> Void in
    self.tableView.reloadData()
},
                          completion: nil);

Swift 3, 4 y 5

UIView.transition(with: tableView,
                  duration: 0.35,
                  options: .transitionCrossDissolve,
                  animations: { self.tableView.reloadData() }) // left out the unnecessary syntax in the completion block and the optional completion parameter

Sin problemas :RE

También puede usar cualquiera de los UIViewAnimationOptionTransitionsque desee para obtener efectos más frescos:

  • transitionNone
  • transitionFlipFromLeft
  • transitionFlipFromRight
  • transitionCurlUp
  • transitionCurlDown
  • transitionCrossDissolve
  • transitionFlipFromTop
  • transitionFlipFromBottom
Kenn Cal
fuente
2
Esto es útil si el estado de inicio / finalización de su vista de tabla será muy diferente (y sería complejo calcular las secciones y filas para agregar / eliminar), pero tiene algo menos discordante que la recarga no animada.
Ben Packard el
1
Esta no es una animación tan agradable como volver a cargar la vista de tabla utilizando sus métodos integrados, pero a diferencia del método de mayor calificación mencionado aquí, este funciona cuando tiene varias secciones.
Mark Bridges
1
Wow, después de probar todo lo demás, esto es definitivamente perfecto para mí
Lucas Goossen
1
@MarkBridges, la respuesta mejor calificada funciona con varias secciones :) -[_tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withRowAnimation:UITableViewRowAnimationFade];
lobianco
2
@ Anthony No funciona si el estado final tiene más / menos secciones que el estado inicial. Luego, tendría que rastrear manualmente qué secciones se agregaron / eliminaron, lo cual es bastante complicado.
nikolovski
78

Tener más libertad usando la CATransitionclase.

No se limita a la decoloración, sino que también puede hacer movimientos.


Por ejemplo:

(no olvides importar QuartzCore)

CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.fillMode = kCAFillModeForwards;
transition.duration = 0.5;
transition.subtype = kCATransitionFromBottom;

[[self.tableView layer] addAnimation:transition forKey:@"UITableViewReloadDataAnimationKey"];

Cambie el typepara que coincida con sus necesidades, como kCATransitionFadeetc.

Implementación en Swift:

let transition = CATransition()
transition.type = kCATransitionPush
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.fillMode = kCAFillModeForwards
transition.duration = 0.5
transition.subtype = kCATransitionFromTop
self.tableView.layer.addAnimation(transition, forKey: "UITableViewReloadDataAnimationKey")
// Update your data source here
self.tableView.reloadData()

Referencia para CATransition

Swift 5:

let transition = CATransition()
transition.type = CATransitionType.push
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.fillMode = CAMediaTimingFillMode.forwards
transition.duration = 0.5
transition.subtype = CATransitionSubtype.fromTop
self.tableView.layer.add(transition, forKey: "UITableViewReloadDataAnimationKey")
// Update your data source here
self.tableView.reloadData()
Matej
fuente
Esto anima la tabla como un todo, en lugar de filas / secciones individuales.
Agos
@Agos Todavía responde la pregunta. Una pregunta "Cómo recargar solo una fila" tiene una respuesta bastante diferente, como aplicar la animación a la layerpropiedad de a UITableViewCell.
Matej
@matejkramny Esperaba que la tabla animara solo las diferentes filas (como se menciona en la pregunta), pero este método empuja toda la tabla a la vez. Tal vez me estoy perdiendo algo?
Agos
@Agos hmm no, se supone que debe hacer eso. No puede hacer solo la mitad de la tabla, ya que QuartzCore modifica la vista directamente. Puede intentar obtener las celdas de la vista de tabla y luego aplicar esta animación a cada una (aunque no estoy seguro de que funcione)
Matej
2
funcionó de maravilla con kCATransitionFade ¡Gracias! :)
quarezz
60

Creo que puede actualizar su estructura de datos y luego:

[tableView beginUpdates];
[tableView deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES];
[tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES];
[tableView endUpdates];

Además, el "withRowAnimation" no es exactamente un estilo booleano, sino un estilo de animación:

UITableViewRowAnimationFade,
UITableViewRowAnimationRight,
UITableViewRowAnimationLeft,
UITableViewRowAnimationTop,
UITableViewRowAnimationBottom,
UITableViewRowAnimationNone,
UITableViewRowAnimationMiddle
Tiago Fael Matos
fuente
Cosas simples, pero tan fáciles de ir directamente a StackOverflow y obtener el fragmento que necesita, que los documentos de arrastre. . ¡Gracias!
Jasper Blues
24

Todas estas respuestas suponen que está utilizando un UITableView con solo 1 sección.

Para manejar con precisión situaciones en las que tiene más de 1 sección, use:

NSRange range = NSMakeRange(0, myTableView.numberOfSections);
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
[myTableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];

(Nota: ¡debes asegurarte de tener más de 0 secciones!)

Otra cosa a tener en cuenta es que puede encontrarse con una excepción NSInternalInconsistencyException si intenta actualizar simultáneamente su fuente de datos con este código. Si este es el caso, puede usar una lógica similar a esta:

int sectionNumber = 0; //Note that your section may be different

int nextIndex = [currentItems count]; //starting index of newly added items

[myTableView beginUpdates];

for (NSObject *item in itemsToAdd) {
    //Add the item to the data source
    [currentItems addObject:item];

    //Add the item to the table view
    NSIndexPath *path = [NSIndexPath indexPathForRow:nextIndex++ inSection:sectionNumber];
    [myTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationAutomatic];
}

[myTableView endUpdates];
diadyne
fuente
3
Buen punto sobre los cálculos de la sección, pero solo tenía una sección y su código tenía un error off-by-one. Tuve que cambiar la primera línea de su código a NSRange range = NSMakeRange (0, myTableView.numberOfSections);
Danyal Aytekin
18

La forma de abordar esto es decirle a tableView que elimine y agregue filas y secciones con el

insertRowsAtIndexPaths:withRowAnimation:`
deleteRowsAtIndexPaths:withRowAnimation:`
insertSections:withRowAnimation:y
deleteSections:withRowAnimation:

métodos de UITableView.

Cuando llame a estos métodos, la tabla animará los elementos que solicitó, luego llamará a reloadData sobre sí mismo para que pueda actualizar el estado después de esta animación. Esta parte es importante: si anima todo pero no cambia los datos devueltos por la fuente de datos de la tabla, las filas aparecerán nuevamente una vez que se complete la animación.

Entonces, el flujo de su aplicación sería:

[self setTableIsInSecondState:YES];

[myTable deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES]];

Mientras los métodos dataSource de su tabla devuelvan el nuevo conjunto correcto de secciones y filas marcando [self tableIsInSecondState](o lo que sea), esto logrará el efecto que está buscando.

iKenndac
fuente
16

No puedo comentar sobre la respuesta principal, pero una implementación rápida sería:

self.tableView.reloadSections([0], with: UITableViewRowAnimation.fade)

puede incluir tantas secciones como desee actualizar en el primer argumento para reloadSections.

Otras animaciones disponibles en los documentos: https://developer.apple.com/reference/uikit/uitableviewrowanimation

fundido La fila o filas insertadas o eliminadas se desvanecen dentro o fuera de la vista de tabla.

right La fila o filas insertadas se deslizan desde la derecha; la fila o filas eliminadas se deslizan hacia la derecha.

left La fila o filas insertadas se deslizan desde la izquierda; la fila o filas eliminadas se deslizan hacia la izquierda.

arriba La fila o filas insertadas se deslizan desde la parte superior; la fila o filas eliminadas se deslizan hacia la parte superior.

parte inferior La fila o filas insertadas se deslizan desde la parte inferior; la fila o filas eliminadas se deslizan hacia la parte inferior.

caso ninguno Las filas insertadas o eliminadas utilizan las animaciones predeterminadas.

middle La vista de tabla intenta mantener las celdas antiguas y nuevas centradas en el espacio que ocuparon o ocuparán. Disponible en iPhone 3.2.

automático La vista de tabla elige un estilo de animación apropiado para usted. (Introducido en iOS 5.0.)

Christopher Larsen
fuente
1
Para Swift 4.2: self.tableView.reloadSections ([0], con: UITableView.RowAnimation.fade)
Reefwing
14

Swift 4 versión para @dmarnel respuesta:

tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
chengsam
fuente
9

Implementación rápida:

let range = NSMakeRange(0, self.tableView!.numberOfSections())
let indexSet = NSIndexSet(indexesInRange: range)
self.tableView!.reloadSections(indexSet, withRowAnimation: UITableViewRowAnimation.Automatic)
Michael Peterson
fuente
8

Para Swift 4

tableView.reloadSections([0], with: UITableView.RowAnimation.fade)
Claus
fuente
1

En mi caso, quería agregar 10 filas más en la vista de tabla (para un tipo de funcionalidad "mostrar más resultados") e hice lo siguiente:

  NSInteger tempNumber = self.numberOfRows;
  self.numberOfRows += 10;
  NSMutableArray *arrayOfIndexPaths = [[NSMutableArray alloc] init];
  for (NSInteger i = tempNumber; i < self.numberOfRows; i++) {
    [arrayOfIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
  }
  [self.tableView beginUpdates];
  [self.tableView insertRowsAtIndexPaths:arrayOfIndexPaths withRowAnimation:UITableViewRowAnimationTop];
  [self.tableView endUpdates];

En la mayoría de los casos, en lugar de "self.numberOfRows", generalmente usaría el recuento de la matriz de objetos para la vista de tabla. Por lo tanto, para asegurarse de que esta solución funcione bien para usted, "arrayOfIndexPaths" debe ser una matriz precisa de las rutas de índice de las filas que se insertan. Si la fila existe para cualquiera de estas rutas de índice, el código puede bloquearse, por lo que debe usar el método "reloadRowsAtIndexPaths: withRowAnimation:" para esas rutas de índice para evitar bloquearse

Lucas
fuente
1

Si desea agregar sus propias animaciones personalizadas a las celdas UITableView, use

[theTableView reloadData];
[theTableView layoutSubviews];
NSArray* visibleViews = [theTableView visibleCells];

para obtener una matriz de celdas visibles. Luego agregue cualquier animación personalizada a cada celda.

Echa un vistazo a esta esencia que publiqué para obtener una animación de celda personalizada suave. https://gist.github.com/floprr/1b7a58e4a18449d962bd

flopr
fuente
1

La animación sin reloadData () en Swift se puede hacer así (a partir de la versión 2.2):

tableview.beginUpdates()
var indexPathsToDeleteForAnimation: [NSIndexPath] = []
var numOfCellsToRemove = ArrayOfItemsToRemove ?? 0

// Do your work here
while numOfCellsToRemove > 0 {
    // ...or here, if you need to add/remove the same amount of objects to/from somewhere
    indexPathsToDeleteForAnimation.append(NSIndexPath(forRow: selectedCellIndex+numOfCellsToRemove, inSection: 0))
    numOfCellsToRemove -= 1
}
tableview.deleteRowsAtIndexPaths(indexPathsToDeleteForAnimation, withRowAnimation: UITableViewRowAnimation.Right)
tableview.endUpdates()

en caso de que necesite llamar a reloadData () después de que finalice la animación, puede aceptar los cambios en CATransaction de esta manera:

CATransaction.begin()
CATransaction.setCompletionBlock({() in self.tableview.reloadData() })
tableview.beginUpdates()
var indexPathsToDeleteForAnimation: [NSIndexPath] = []
var numOfCellsToRemove = ArrayOfItemsToRemove.count ?? 0

// Do your work here
while numOfCellsToRemove > 0 {
     // ...or here, if you need to add/remove the same amount of objects to/from somewhere
     indexPathsToDeleteForAnimation.append(NSIndexPath(forRow: selectedCellIndex+numOfCellsToRemove, inSection: 0))
     numOfCellsToRemove -= 1
}
tableview.deleteRowsAtIndexPaths(indexPathsToDeleteForAnimation, withRowAnimation: UITableViewRowAnimation.Right)
tableview.endUpdates()
CATransaction.commit()

La lógica se muestra para el caso cuando elimina filas, pero la misma idea también funciona para agregar filas. También puede cambiar la animación a UITableViewRowAnimation.Left para que quede ordenada o elegir de la lista de otras animaciones disponibles.

Vitalii
fuente
1
CATransition *animation = [CATransition animation];
animation.duration = .3;
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromLeft];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[animation setDuration:.3];

[[_elementTableView layer] addAnimation:animation forKey:@"UITableViewReloadDataAnimationKey"];

[tableView reloadData];
Mayur Sardana
fuente
2
¿Por qué establecer la duración .3dos veces?
Pang
1

Para recargar todas las secciones , no solo una con duración personalizada .

durationParámetro de usuario UIView.animatepara establecer la duración personalizada.

UIView.animate(withDuration: 0.4, animations: { [weak self] in
    guard let `self` = self else { return }
    let indexSet = IndexSet(integersIn: 0..<self.tableView.numberOfSections)
    self.tableView.reloadSections(indexSet, with: UITableView.RowAnimation.fade)
})
JPetric
fuente
0

UITableViewAnimaciones nativas en Swift

Inserte y elimine filas todas a la vez con tableView.performBatchUpdatespara que ocurran simultáneamente. Partiendo de la respuesta de @iKenndac, utilice métodos como:

  • tableView.insertSections
  • tableView.insertRows
  • tableView.deleteSections
  • tableView.deleteRows

Ex:

 tableView.performBatchUpdates({
   tableView.insertSections([0], with: .top)
 })

Esto inserta una sección en la posición cero con una animación que se carga desde la parte superior. Esto volverá a ejecutar el cellForRowAtmétodo y buscará una nueva celda en esa posición. Puede volver a cargar toda la vista de la tabla de esta manera con animaciones específicas.

Re: la pregunta OP, se habría necesitado un indicador condicional para mostrar las celdas para el estado de vista de tabla alternativa.

arte
fuente