ios - custom - uicollectionview header
¿Cómo se establece la duración de UICollectionView Animations? (8)
Tengo un diseño de flujo personalizado que está ajustando los atributos de las celdas cuando se están insertando y eliminando de CollectionView con las dos funciones siguientes, pero no puedo imaginar cómo ajustarías la duración predeterminada de la animación.
- (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;
}
UICollectionView
inicia todas las animaciones internamente utilizando algún valor codificado. Sin embargo, siempre puede anular ese valor hasta que se cometan las animaciones. En general, el proceso se ve así:
- comenzar animaciones
- buscar todos los atributos attribues
- aplicar atributos a las vistas (UICollectionViewCell''s)
- cometer animaciones
la aplicación de atributos se realiza debajo de cada UICollectionViewCell y puede anular animationDuration en el método apropiado. El problema es que UICollectionViewCell tiene el método público applyLayoutAttributes:
PERO su implementación predeterminada está vacía !. Básicamente, UICollectionViewCell tiene otro método privado llamado _setLayoutAttributes:
este método privado es llamado por UICollectionView y este método privado llama applyLayoutAttributes:
al final. Los atributos de diseño predeterminados, como marco, posición, transformación se aplican con la animationDuration
applyLayoutAttributes:
antes de applyLayoutAttributes:
se llama. Dicho esto, debes anular animationDuration
en el método privado _setLayoutAttributes:
- (void) _setLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes
{
[UIView setAnimationDuration:3.0];
[super _setLayoutAttributes:layoutAttributes];
}
Esto es obviamente, no apto para manzanas. Puede utilizar uno de esos hacks en tiempo de ejecución para anular este método privado de forma segura.
Después de probar [CATransaction setAnimationDuration:]
y [UIView setAnimationDuration:]
en todas las fases posibles del proceso de diseño sin éxito, descubrí una forma un tanto chiflada de cambiar la duración de las animaciones de celda creadas por UICollectionView
que no dependen de API privadas.
Puede usar la propiedad de speed
CALayer
para cambiar el tiempo de medios relativo de las animaciones realizadas en una capa determinada. Para que esto funcione con UICollectionView
, puede cambiar layer.speed
a algo menos que 1 en la capa de la celda. Obviamente, no es genial tener SIEMPRE la capa de la celda con una velocidad de animación no unitaria, por lo que una opción es enviar una NSNotification
cuando se preparen para las animaciones celulares, a las que sus células se suscriban, que cambie la velocidad de la capa y luego cambiarla en un momento apropiado después de que las animaciones hayan terminado.
No recomiendo usar este enfoque como una solución a largo plazo, ya que es bastante indirecto, pero funciona. Con suerte, Apple expondrá más opciones para las animaciones UICollectionView en el futuro.
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;
}
Puede cambiar la propiedad UICollectionView layout.speed, que debería cambiar la duración de la animación de su diseño ...
Basándose en la respuesta de @ rotava, puede establecer temporalmente la velocidad de la animación mediante una actualización por lotes de la vista de 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];
}];
Para resolver el problema sin hack que se propuso en la respuesta de gavrix , podría subclase UICollectionViewLayoutAttributes con la nueva propiedad CABasicAnimation *transformAnimation
, que crear una transformación personalizada con una duración adecuada y asignarla a atributos en initialLayoutAttributesForAppearingItemAtIndexPath
, luego en UICollectionViewCell aplicar los atributos 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 clase de diseño
- (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 aplica los atributos
- (void) applyLayoutAttributes:(AnimationCollectionViewLayoutAttributes *)layoutAttributes
{
[[self layer] addAnimation:layoutAttributes.transformAnimation forKey:@"transform"];
}
Puede establecer la propiedad de velocidad de la capa (como en [Respuesta de Rotoava]) ( https://.com/a/23146861/5271393 ) para cambiar el control de la velocidad de la animación. El problema es que está utilizando valores arbitrarios porque no conoce la duración real de la animación de inserción.
Usando esta publicación puedes averiguar cuál es la duración de la animación predeterminada.
newAnimationDuration = (1/layer.speed)*originalAnimationDuration
layer.speed = originalAnimationDuration/newAnimationDuration
Si quisieras hacer la animación de 400 ms de largo, en tu diseño, harías lo siguiente:
- (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 células que podrían ser arrastradas fuera de la pantalla y quería cambiar la duración de la animación de eliminación en función de la velocidad del gesto panorámico.
En el reconocedor de gestos (que debe ser parte de su 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;
}
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
})