Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iphone : remove CALayer when animation stop, CALayer flash before disappear

  • create a simple project in XCode
  • set view to receive multi-touch events
  • respond in touchesBegan, create CALayer when detect touch event
  • make a opacity fade out animation for CALayer
  • when animation stop, remove CALayer from parent

Expect: CALayer disappear normally

Actual: CALayer flash (blink) before disappear

full source code:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    self.view.multipleTouchEnabled = YES;
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    for (UITouch* touch in touches) {
        CGPoint p = [touch locationInView:self.view];

        //NSLog(@"touch=%@ p=%@", touch, NSStringFromCGPoint(p));

        CALayer *layer = [CALayer layer];
        layer.position = p;
        layer.bounds = CGRectMake(0, 0, 70, 70);
        layer.cornerRadius = 30;
        layer.masksToBounds = NO;
        layer.backgroundColor = [UIColor colorWithRed:102.0/255.0 green:156.0/255.0 blue:255.0/255.0 alpha:0.8].CGColor;
        layer.shouldRasterize = YES;

        CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        fadeOutAnimation.fromValue = [NSNumber numberWithFloat:1.0];
        fadeOutAnimation.toValue = [NSNumber numberWithFloat:0.0];
        fadeOutAnimation.duration = 0.5;
        fadeOutAnimation.delegate = self;
        fadeOutAnimation.removedOnCompletion = NO;
        [fadeOutAnimation setValue:layer forKey:@"parentLayer"];
        [layer addAnimation:fadeOutAnimation forKey:@"opacity"];

        [self.view.layer addSublayer:layer];
    }
}

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
    if(flag) {
        CALayer *layer = [theAnimation valueForKey:@"parentLayer"];
        if(layer) {
            layer.opaque = NO;
            layer.opacity = 0.0;
            //layer.hidden = YES;
            //NSLog(@"The layer object was: %@ (%@)", layer, [layer name]);
            [layer removeFromSuperlayer];
            [layer removeAllAnimations];
        }
    }
}

@end
like image 566
Rui Fan Avatar asked May 15 '12 14:05

Rui Fan


1 Answers

tl;dr: Set the fillMode on the animation to kCAFillModeForwards or change the values to their final value prior to adding the animation to the layer.


A basic animation is only a visual animation during the time of the animation, no actual values are changed. When you set the animation to not be removed upon completion it means that the layer will still reference the animation object as one of its animations. It has however already ran its animation.

The default behavior of how animations look (their fill mode) is kCAFillModeRemoved which means that just after the duration of the animation the layer will look as if the animation never happened. By changing the fill mode to either kCAFillModeForwards or kCAFillModeBoth you can make the layer look as if the layer remained in the end state of the animation.

You can do the same thing in the beginning of the animation with kCAFillModeBackwards but it mostly applies when you have set a begin time for the animation.

So to make the animation look as it did in the final state of the animation you can either set the fill mode to ...Forwards and not remove the animation or change the actual values of the layer to the values you expect them to be just before you add the animation to the view. This will change the values and then animate from the old value to the new.

like image 164
David Rönnqvist Avatar answered Sep 18 '22 13:09

David Rönnqvist