CABasicAnimation flicker when applying the completion
Asked Answered
R

2

10

I am trying to apply a rotation animation by number of degrees to a UIImageView and persist the rotation transformation in the completion block.

The problem that I am facing is that when the completion block is executed there is a visible flicker generated by passing from the end state of the animation to the completion block.

Here is the code that I am currently using:

if (futureAngle == currentAngle) {
    return;
}

float rotationAngle;
if (futureAngle < currentAngle) {
    rotationAngle = futureAngle - currentAngle;
}else{
    rotationAngle = futureAngle - currentAngle;
}

float animationDuration = fabs(rotationAngle) / 100;
rotationAngle = GLKMathDegreesToRadians(rotationAngle);

[CATransaction begin];
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.byValue = [NSNumber numberWithFloat:rotationAngle];
rotationAnimation.duration = animationDuration;
rotationAnimation.removedOnCompletion = YES;

[CATransaction setCompletionBlock:^{
    view.transform = CGAffineTransformRotate(view.transform, rotationAngle);
}];

[view.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
[CATransaction commit];
Rawden answered 28/6, 2016 at 16:7 Comment(3)
When you say flicker, I assume you mean that at the end of the animation, that it momentarily returns to the initial state before returning back to the final state? This can be solved either by (a) setting the final view.transform before you start the animation (and you no longer need the completionBlock); or (b) by setting the animation's fillMode to kCAFillModeForwards and set removedOnCompletion to false.Architectonic
See oleb.net/blog/2012/11/prevent-caanimation-snap-back or https://mcmap.net/q/1160890/-flickering-in-cabasicanimation-for-rotation or https://mcmap.net/q/430065/-cabasicanimation-rotate-returns-to-original-positionArchitectonic
@Architectonic can you provide an example for the suggestion regarding the completion block? I tried the fillMode to kCAFillModeForwards and set removedOnCompletion to false before creating the post and they didn't worked for me.Rawden
A
27

When you say flicker, I assume you mean that at the end of the animation, that it momentarily returns to the initial state before returning back to the final state? This can be solved either by

  • setting the final view.transform before you start the animation (and you no longer need the completionBlock);
  • by setting the animation's fillMode to kCAFillModeForwards and set removedOnCompletion to false.

Personally, I think setting the animated property to its destination value before you start the animation is the easiest way to do this.

Thus:

- (void)rotate:(UIView *)view by:(CGFloat)delta {
    float animationDuration = 2.0;
    CGFloat currentAngle = self.angle;                                                    // retrieve saved angle
    CGFloat nextAngle = self.angle + delta;                                               // increment it
    self.angle = nextAngle;                                                               // save new value

    view.transform = CGAffineTransformMakeRotation(nextAngle);                            // set property to destination rotation

    CABasicAnimation *rotationAnimation;
    rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];  // now rotate
    rotationAnimation.fromValue = @(currentAngle);
    rotationAnimation.toValue = @(nextAngle);
    rotationAnimation.duration = animationDuration;
    rotationAnimation.removedOnCompletion = YES;
    [view.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
}

Or, I think even easier, just adjust the transform:

- (void)rotate:(UIView *)view by:(CGFloat)delta {
    float animationDuration = 2.0;
    CGAffineTransform transform = view.transform;                                         // retrieve current transform
    CGAffineTransform nextTransform = CGAffineTransformRotate(transform, delta);          // increment it

    view.transform = nextTransform;                                                       // set property to destination rotation

    CABasicAnimation *rotationAnimation;
    rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];             // now rotate
    rotationAnimation.fromValue = [NSValue valueWithCGAffineTransform:transform];
    rotationAnimation.toValue = [NSValue valueWithCGAffineTransform:nextTransform];
    rotationAnimation.duration = animationDuration;
    rotationAnimation.removedOnCompletion = YES;
    [view.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
}
Architectonic answered 29/6, 2016 at 22:55 Comment(2)
Fascinating, it would seem that if you are trying to do a UIView.animate at the same time (say, just moving a constraint), the "fillMode approach" seems to be the answer .........Neustria
Thanks Rob! Somehow the first option doesn't work for me, but the second one does.Peoples
B
6

I was seeing flickering even when using the suggested answer from Rob, but turns out it seems to just be a simulator bug. On real devices I dont see the flicker, if you have only been testing on simulator, try on a real device unless you want to waste hours of your life potentially like myself.

Bongbongo answered 6/10, 2020 at 9:50 Comment(1)
Oh my god, you literally saved me tons of time. On simulator the animation flickers / blinks, but on real device it just works. It take me a while to realize that maybe there is no problem with my code, and the solution lies somewhere else. Very-very big thanks!Pelham

© 2022 - 2024 — McMap. All rights reserved.