UIViewAnimationOptionBeginFromCurrentState comportamiento inesperado con animation básica

Estoy intentando realizar esta animation básica de UIView al recibir un clic de button:

 - (IBAction)buttonPress:(id)sender { self.sampleView.alpha = 0.0; [UIView animateWithDuration:2.0 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState animations:^{self.sampleView.alpha = 1.0;} completion:NULL]; } 

Antes de hacer clic en el button, la vista es visible y tiene un valor alfa de 1.0. Cuando envío la acción del button espero que la vista se desvanezca de alpha = 0.0 a alpha = 1.0 en 2 segundos, pero esto no sucede. Cuando UIViewAnimationOptionBeginFromCurrentState la animation funciona bien.

Parece que con la opción UIViewAnimationOptionBeginFromCurrentState configurada, el alpha = 0.0 no se está configurando y la animation se omite porque cree que ya está en 1.0.

Estoy tratando de entender por qué esto sucedería ya que la documentation de Apple indica que UIViewAnimationOptionBeginFromCurrentState no tiene efecto si otra animation no está en progreso:

" UIViewAnimationOptionBeginFromCurrentState Inicie la animation desde la configuration actual asociada a una animation ya en el vuelo. Si esta tecla no está presente, se permite que todas las animaciones en el vuelo finalicen antes de que se inicie la nueva animation. Si otra animation no está en vuelo, esto La key no tiene efecto ".

Resulta que usar UIViewAnimationOptionBeginFromCurrentState no siempre funciona como se esperaba.

Mira este ejemplo:

 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; UIButton *theButton = [UIButton new]; [self.view addSubview:theButton]; theButton.frame = self.view.frame; theButton.backgroundColor = [UIColor networkingColor]; theButton.alpha = 0.05; [theButton addTarget:self action:@selector(actionPressed:) forControlEvents:UIControlEventTouchUpInside]; } - (void)actionPressed:(UIButton *)theButton { theButton.alpha = 0.6; [UIView animateWithDuration:5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { // you expect to animate from 0.6 to 1. but it animates from 0.05 to 1 theButton.alpha = 1; } completion:nil]; } 

En el ejemplo anterior, esperas que .alpha se anime de 0.6 a 1. Sin embargo, anima de 0.05 a 1.

Para resolver el problema, debe cambiar actionPressed: a lo siguiente:

 - (void)actionPressed:(UIButton *)theButton { [UIView animateWithDuration:0 animations:^ { // set new values INSIDE of this block, so that changes are // captunetworking in UIViewAnimationOptionBeginFromCurrentState. theButton.alpha = 0.6; } completion:^(BOOL finished) { [UIView animateWithDuration:5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { // now it really animates from 0.6 to 1 theButton.alpha = 1; } completion:nil]; }]; } 

Mencione animateWithDuration:0 !!!

La regla es fácil: solo use un bloque de animation con UIViewAnimationOptionBeginFromCurrentState DESPUÉS de algún otro bloque de animation, de modo que todos sus cambios anteriores se apliquen realmente.

En caso de que no sepas qué debería includese exactamente en animateWithDuration:0 bloque animateWithDuration:0 , entonces puedes usar este truco:

 - (void)actionPressed:(UIButton *)theButton { // make all your changes outside of the animation block theButton.alpha = 0.6; // create a fake view and add some animation to it. UIView *theFakeView = [UIView new]; theFakeView.alpha = 1; [UIView animateWithDuration:0 animations:^ { // we need this line so that all previous changes are ACTUALLY applied theFakeView.alpha = 0; } completion:^(BOOL finished) { [UIView animateWithDuration:5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { // now it really animates from 0.6 to 1 theButton.alpha = 1; } completion:nil]; }]; } 

Si no quiere recordar todos los detalles de este error, simplemente aplique el Método Swizzling a la class UIView.

EDIT IMPORTANTE: Resulta que el código siguiente causará un locking en el time de ejecución , si se llama a performBatchUpdates:completion: de la instancia de UICollectionView . Por lo tanto, ¡no recomiendo utilizar el method swizzling en este caso!

Su código puede verse así:

 + (void)load { static dispatch_once_t theOnceToken; dispatch_once(&theOnceToken, ^ { Class theClass = object_getClass(self); SEL theOriginalSelector = @selector(animateWithDuration:delay:options:animations:completion:); SEL theSwizzledSelector = @selector(swizzled_animateWithDuration:delay:options:animations:completion:); Method theOriginalMethod = class_getClassMethod(theClass, theOriginalSelector); Method theSwizzledMethod = class_getClassMethod(theClass, theSwizzledSelector); if (!theClass ||!theOriginalSelector || !theSwizzledSelector || !theOriginalMethod || !theSwizzledMethod) { abort(); } BOOL didAddMethod = class_addMethod(theClass, theOriginalSelector, method_getImplementation(theSwizzledMethod), method_getTypeEncoding(theSwizzledMethod)); if (didAddMethod) { class_replaceMethod(theClass, theSwizzledSelector, method_getImplementation(theOriginalMethod), method_getTypeEncoding(theOriginalMethod)); } else { method_exchangeImplementations(theOriginalMethod, theSwizzledMethod); } }); } + (void)swizzled_animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL))completion { if (options & UIViewAnimationOptionBeginFromCurrentState) { UIView *theView = [UIView new]; theView.alpha = 1; [UIView animateWithDuration:0 animations:^ { theView.alpha = 0; } completion:^(BOOL finished) { [self swizzled_animateWithDuration:duration delay:delay options:options animations:animations completion:completion]; }]; } else { [self swizzled_animateWithDuration:duration delay:delay options:options animations:animations completion:completion]; } } 

Si agrega este código a su categoría UIView personalizada, este código funcionaría bien ahora:

 - (void)actionPressed:(UIButton *)theButton { theButton.alpha = 0.6; [UIView animateWithDuration:5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { // you expect to animate from 0.6 to 1. // it will do so ONLY if you add the above code sample to your project. theButton.alpha = 1; } completion:nil]; } 

En mi caso, este error ha arruinado por completo mis animaciones. Vea abajo:

[ Demo CountPages alfa ] [ Demo CountPages alfa ]

Para ver lo que está sucediendo, es útil animar la position del marco en lugar de la alfa. Si lo hace, intente tocar el button cada segundo con UIViewAnimationOptionBeginFromCurrentState y comenzará a ver algunos movimientos.

Cuando configura el alfa a cero, esto no actualiza la pantalla hasta el siguiente paso a través del ciclo de ejecución. Dado que comienza su animation inmediatamente después, la pantalla no tiene la oportunidad de actualizarse y cuando comienza el marco de animation, mira el alfa (1.0) actual que se muestra y mira dónde tiene que llegar (alfa de 1.0) y la interpolación entre los dos no hace nada.

Si necesita mantener la opción UIViewAnimationOptionBeginFromCurrentState allí porque este button se puede tocar cuando una animation en esta vista ya está en progreso, puede hacer esto:

 - (IBAction)buttonPressed:(id)sender { self.sampleView.alpha = 0.0; [self performSelector:@selector(animateAlpha) withObject:nil afterDelay:0.1]; } - (void)animateAlpha { [UIView animateWithDuration:2.0 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState animations:^{self.sampleView.alpha = 1.0;} completion:NULL]; } 

Al usar UIViewAnimationOptionBeginFromCurrentState , en lugar de performSelector: me gusta establecer valores iniciales con UIView 's performWithoutAnimation justo antes de llamar animateWithDuration: