¿Cómo configura la duración de UICollectionView Animations?

Tengo un layout de flujo personalizado que está ajustando los attributes para las celdas cuando se insertan y se eliminan de CollectionView con las dos funciones siguientes, pero no puedo determinar cómo ajustarías la duración de la animation pnetworkingeterminada.

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath { UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; // Assign the new layout attributes attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5); attributes.alpha = 0; return attributes; 

}

 -(UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{ UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; // Assign the new layout attributes attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5); attributes.alpha = 0; return attributes; 

}

Para resolver el problema sin hack propuesto en la respuesta por gavrix , puede subclass UICollectionViewLayoutAttributes con la nueva propiedad CABasicAnimation *transformAnimation , que cree una transformación personalizada con una duración adecuada y asígnele attributes en initialLayoutAttributesForAppearingItemAtIndexPath , luego en UICollectionViewCell, aplique los attributes según sea necesario:

 @interface AnimationCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes @property (nonatomic, strong) CABasicAnimation *transformAnimation; @end @implementation AnimationCollectionViewLayoutAttributes - (id)copyWithZone:(NSZone *)zone { AnimationCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone]; attributes.transformAnimation = _transformAnimation; return attributes; } - (BOOL)isEqual:(id)other { if (other == self) { return YES; } if (!other || ![[other class] isEqual:[self class]]) { return NO; } if ([(( AnimationCollectionViewLayoutAttributes *) other) transformAnimation] != [self transformAnimation]) { return NO; } return YES; } @end 

En la class de layout

 - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath { AnimationCollectionViewLayoutAttributes* attributes = (AnimationCollectionViewLayoutAttributes* )[super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; transformAnimation.duration = 1.0f; CGFloat height = [self collectionViewContentSize].height; transformAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, 2*height, height)]; transformAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, attributes.bounds.origin.y, 0)]; transformAnimation.removedOnCompletion = NO; transformAnimation.fillMode = kCAFillModeForwards; attributes.transformAnimation = transformAnimation; return attributes; } 

luego en UICollectionViewCell aplicar los attributes

 - (void) applyLayoutAttributes:(AnimationCollectionViewLayoutAttributes *)layoutAttributes { [[self layer] addAnimation:layoutAttributes.transformAnimation forKey:@"transform"]; } 

cambiar la velocidad de CALayer

 @implementation Cell - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.layer.speed =0.2;//default speed is 1 } return self; } 

Basándose en la respuesta de @ rotava, puede establecer temporalmente la velocidad de animation mediante una actualización por lotes de la vista de la colección:

 [self.collectionView performBatchUpdates:^{ [self.collectionView.viewForBaselineLayout.layer setSpeed:0.2]; [self.collectionView insertItemsAtIndexPaths: insertedIndexPaths]; } completion:^(BOOL finished) { [self.collectionView.viewForBaselineLayout.layer setSpeed:1]; }]; 

Después de probar [CATransaction setAnimationDuration:] y [UIView setAnimationDuration:] en todas las fases posibles del process de layout sin éxito, descubrí una forma algo hackeable de cambiar la duración de las animaciones de celda creadas por UICollectionView que no se basan en API privadas.

Puede usar la propiedad de speed CALayer para cambiar el time relativo de los medios de las animaciones realizadas en una capa determinada. Para que esto funcione con UICollectionView , puede cambiar layer.speed a algo less de 1 en la capa de la celda. Obviamente no es genial tener la capa de la celda SIEMPRE tenga una velocidad de animation no unitaria, por lo que una opción es despachar una NSNotification cuando se prepara para las animaciones de celda, a las cuales sus células se suscriben, eso cambiará la velocidad de la capa y luego la cambiará de nuevo en un momento apropiado después de que las animaciones hayan terminado.

No recomiendo utilizar este enfoque como una solución a largo ploop, ya que es bastante indirecto, pero funciona. Esperemos que Apple exponga más opciones para las animaciones UICollectionView en el futuro.

UICollectionView inicia todas las animaciones internamente usando algún valor codificado. Sin embargo, siempre puedes anular ese valor hasta que se confirmen las animaciones. En general, el process se ve así:

  • comenzar animaciones
  • search todas las attribues de layout
  • aplicar attributes a las vistas (UICollectionViewCell's)
  • cometer animaciones

la aplicación de attributes se realiza bajo cada UICollectionViewCell y puede anular la animationDuración en el método apropiado. El problema es que UICollectionViewCell tiene un método público applyLayoutAttributes: PERO su implementación pnetworkingeterminada está vacía !. Básicamente, UICollectionViewCell tiene otro método privado llamado _setLayoutAttributes: llama a este método privado y este método privado llama a applyLayoutAttributes: al final. Los attributes de layout pnetworkingeterminados, como el marco, la position, la transformación se aplican con la animationDuration applyLayoutAttributes: antes de applyLayoutAttributes: se applyLayoutAttributes: . Dicho esto, debe anular animationDuration en el método privado _setLayoutAttributes:

 - (void) _setLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes { [UIView setAnimationDuration:3.0]; [super _setLayoutAttributes:layoutAttributes]; } 

Obviamente, esto no es seguro. Puede usar uno de esos hacks de time de ejecución para anular este método privado de forma segura.

Puede configurar la propiedad de velocidad de la capa (como en [Respuesta de Rotoava]) ( https://stackoverflow.com/a/23146861/5271393 ) para cambiar el control de la velocidad de la animation. El problema es que está utilizando valores arbitrarios porque no conoce la duración real de la animation de inserción.

Usando esta publicación puedes averiguar cuál es la duración de la animation pnetworkingeterminada.

 newAnimationDuration = (1/layer.speed)*originalAnimationDuration layer.speed = originalAnimationDuration/newAnimationDuration 

Si desea hacer la animation 400 ms de largo, en su layout, usted:

 - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath]; //set attributes here UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; CGFloat originalAnimationDuration = [CATransaction animationDuration]; CGFloat newAnimationDuration = 0.4f; cell.layer.speed = originalAnimationDuration/newAnimationDuration; return attributes; } 

En mi caso, tenía celdas que podían arrastrarse fuera de la pantalla y quería cambiar la duración de la animation de borrado en function de la velocidad del gesto de paneo.

En el reconocimiento de gestos (que debe ser parte de la vista de colección):

 - (void)handlePanGesture:(UIPanGestureRecognizer *)sender { CGPoint dragVelocityVector = [sender velocityInView:self.collectionView]; CGFloat dragVelocity = sqrt(dragVelocityVector.x*dragVelocityVector.x + dragVelocityVector.y*dragVelocityVector.y); switch (sender.state) { ... case UIGestureRecognizerStateChanged:{ CustomLayoutClass *layout = (CustomLayoutClass *)self.collectionViewLayout; layout.dragSpeed = fabs(dragVelocity); ... } ... } 

Luego en su customLayout:

 - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath]; CGFloat animationDistance = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); CGFloat originalAnimationDuration = [CATransaction animationDuration]; CGFloat newAnimationDuration = animationDistance/self.dragSpeed; UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; cell.layer.speed = originalAnimationDuration/newAnimationDuration; return attributes; } 

Puede cambiar la propiedad UICollectionView layout.speed, que debería cambiar la duración de la animation de su layout …

Una actualización de @AshleyMills ya que forBaselineLayout está en desuso.

Esto funciona

 self.collectionView.performBatchUpdates({ () -> Void in let indexSet = IndexSet(0...(numberOfSections - 1)) self.collectionView.insertSections(indexSet) self.collectionView.forFirstBaselineLayout.layer.speed = 0.5 } , completion: { (finished) -> Void in self.collectionView.forFirstBaselineLayout.layer.speed = 1.0 })