Animar el cambio de marco UICollectionView al insert celdas

Quiero cambiar el tamaño de fotogtwig de un UICollectionView en una animation que se ejecuta junto a una inserción de celda animada en la misma vista de colección dentro de un performBatchUpdates:completion: block.

Este es el código que activa la inserción de la celda:

 [collectionView performBatchUpdates:^{ indexPathOfAddedCell = ...; [collectionView insertItemsAtIndexPaths:@[ indexPathOfAddedCell ]]; } completion:nil]; 

Debido a que la inserción de la celda hace que el contentSize la vista de la colección cambie, probé el logging de KVO para los cambios en esa propiedad y luego activé la actualización de la vista de la colección desde el manejador de KVO.

El problema con este enfoque es que el desencadenante de KVO para contentSize dispara demasiado tarde: la animation de inserción de celdas ya se ha completado en ese momento (en realidad, KVO se dispara justo antes del manejador de finalización de performBatchUpdates:completion: se llama pero después de que se ha reproducido la animation en la interfaz de usuario).

No estoy usando el layout automático.

Editar: Puse un proyecto de ejemplo para demostrar mi problema en GitHub.

Edición 2: Debo mencionar que necesito esto para un componente que estoy escribiendo ( OLEContainerScrollView ) que se supone que es 100% independiente de la vista de colección. Debido a esto, no puedo subclasificar el layout de vista de colección, ni tengo influencia sobre el código que activa las animaciones de celda. Idealmente, una solución también funcionaría para UITableView , que exhibe el mismo comportamiento.

Miré cómo ambas vistas de colección y vistas de tabla actualizan su inserción de contenido y, de hecho, el tamaño de contenido de la vista de desplazamiento solo se actualiza después de que la animation se completa en ambos casos. No parece haber un método realmente bueno para escuchar el tamaño de contenido futuro sin usar API privada, pero es posible.

Para las vistas de tabla, el desencadenador para el inicio de la animation es -[UITableView _endCellAnimationsWithContext:] . Este método configura todas las animaciones necesarias (para las futuras celdas visibles solamente), las ejecuta y establece un bloque de finalización que finalmente llama -[UITableView _updateContentSize] . _updateContentSize utiliza el método interno -[UITableView _contentSize] para establecer el tamaño correcto del contenido de la vista de desplazamiento. Dado que _endCellAnimationsWithContext: trata solo con animaciones, los datos que se encuentran detrás de la vista de tabla ya están actualizados, por lo que llamar a _contentSize (o usar valueForKey:@"_contentSize" ) devuelve un tamaño correcto.

Es muy similar para las vistas de colección. El disparador es -[UICollectionView _endItemAnimations] , comienza muchas animaciones para cada celda, encabezado y pie de página, y cuando todas las animaciones terminan, -[UICollectionView _updateAnimationDidStop:finished:context:] establece el tamaño de contenido correcto. Debido a que esta es una vista de colección, el layout de la vista de colección realmente conoce el tamaño del contenido de destino, por lo que puede llamar a -[UICollectionViewLayout collectionViewContentSize] para get el tamaño de contenido actualizado.

Ninguna de estas opciones es realmente buena para usar en la tienda de aplicaciones. Una opción en la que puedo pensar es que ISA swizzling cada subclass de vista de desplazamiento agregada y envolver todos los puntos de input animables, rastreando si están agrupados o no, y al final del lote o al final de la operación animada independiente, usa los methods correspondientes ( -[UITableView _contentSize] y -[UICollectionViewLayout collectionViewContentSize] ) para get el tamaño de contenido de destino.


Respuesta original, en caso de que desee saber acerca de los cambios en el tamaño de una vista de colección en sus propias vistas de colección:

Subclass el layout de la vista de la colección (si aún no lo ha hecho), y usando ya sea el centro de notifications o un método de delegado, notifique en prepareForAnimatedBoundsChange: para otras animaciones. Se agregarán al bloque de animation.

De la documentation:

También puede usar este método para realizar animaciones adicionales. Todas las animaciones que cree se agregan al bloque de animation utilizado para manejar los cambios de inserciones, eliminaciones y límites.

Es posible que deba determinar cuáles son los cambios y solo notificar sobre las animaciones de inserción.

Analicé su proyecto de demostración y creo que no hay necesidad de KVO. Si desea cambiar el marco de la vista de colección con animation mientras inserta una nueva celda, creo que puede hacer algo como esto:

 #import "ViewController.h" @interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate> @property (weak, nonatomic) IBOutlet UICollectionView *collectionView; @property (weak, nonatomic) IBOutlet UIView *otherView; @property (nonatomic) NSInteger numberOfItemsInCollectionView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.numberOfItemsInCollectionView = 1; // This is our model } - (IBAction)addItem:(id)sender { // Change the model self.numberOfItemsInCollectionView += 1; [UIView animateWithDuration:0.24 animations:^{ NSIndexPath *indexPathOfInsertedCell = [NSIndexPath indexPathForItem:self.numberOfItemsInCollectionView - 1 inSection:0]; [self.collectionView insertItemsAtIndexPaths:@[ indexPathOfInsertedCell ]]; CGRect collectionViewFrame = self.collectionView.frame; collectionViewFrame.size.height = (self.numberOfItemsInCollectionView * 40) + 94; self.collectionView.frame = collectionViewFrame; }]; } #pragma mark - UICollectionViewDataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.numberOfItemsInCollectionView; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; { UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath]; return cell; } @end 

O si quieres efecto de desvanecimiento:

 - (IBAction)addItem:(id)sender { // Change the model self.numberOfItemsInCollectionView += 1; CATransition *transition = [CATransition animation]; transition.type = kCATransitionFade; transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; transition.fillMode = kCAFillModeForwards; transition.duration = 0.5; [[self.collectionView layer] addAnimation:transition forKey:@"UICollectionViewInsertRowAnimationKey"]; NSIndexPath *indexPathOfInsertedCell = [NSIndexPath indexPathForItem:self.numberOfItemsInCollectionView - 1 inSection:0]; [self.collectionView insertItemsAtIndexPaths:@[ indexPathOfInsertedCell ]]; CGRect collectionViewFrame = self.collectionView.frame; collectionViewFrame.size.height = (self.numberOfItemsInCollectionView * 40) + 94; self.collectionView.frame = collectionViewFrame; } 

He comprobado esto con tu proyecto de demostración, y funciona para mí. Intenta esto y comenta si también funciona para ti.

¿Por qué no simplemente agregar un bloque de animation para cambiar el marco de la vista de colección?
Esto produce un cambio de límites animados que va bien junto con el desvanecimiento de inserción. El material KVO no es necesario para esta solución.

EDITAR: Podrías activar ese código cuando modificas los cambios.

Aquí está el código relevante para su proyecto de ejemplo:

 - (IBAction)addItem:(id)sender { // Change the model self.numberOfItemsInCollectionView += 1; // Animate cell insertion [self.collectionView performBatchUpdates:^{ NSIndexPath *indexPathOfInsertedCell = [NSIndexPath indexPathForItem:self.numberOfItemsInCollectionView - 1 inSection:0]; [self.collectionView insertItemsAtIndexPaths:@[ indexPathOfInsertedCell ]]; } completion:nil]; // animate the collection view's frame [UIView animateWithDuration:.5 animations:^{ CGRect collectionViewFrame = self.collectionView.frame; collectionViewFrame.size.height = (self.numberOfItemsInCollectionView * 40) + 94; self.collectionView.frame = collectionViewFrame; }]; }