2

I am trying to animate a CAEmitterLayer's emitterPosition like this:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"emitterPosition.x"] ;
animation.toValue = (id) toValue ;
animation.removedOnCompletion = NO ;
animation.duration = self.translationDuration ;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut] ;
animation.completion = ^(BOOL finished)
    {
    [self animateToOtherSide] ;
    } ;
[_emitterLayer addAnimation:animation forKey:@"emitterPosition"] ;
CGPoint newEmitterPosition = CGPointMake(toValue.floatValue, self.bounds.size.height/2.0) ;
_emitterLayer.emitterPosition = newEmitterPosition ;

Note that animation.completion is declared in a category that just calls the corresponding CAAnimation delegate method.

The problem is that this doesn't animate at all and shows the emitter in its final position. I was under the impression that once you add the animation to the layer, you should change the actual model behind it to its final position so that when the animation completes the model is in its final state; i.e., to prevent the animation from "snapping back" to its original position.

I have tried placing the last two lines in the animation.completion block, and this does indeed animate as expected. However, when the animation finishes some particles are intermittently emitted at the emitter's original position. If you put the system under load (for example, scrolling a tableview while the animation is playing), this happens more often.

Another solution I was thinking about is to not move the emitterPosition at all but just move the CAEmitterLayer itself, although I haven't tried that yet.

Thanks in advance for any help you can provide.

4

1 に答える 1

6

Perhaps emitterPosition.x is not a valid key path for animation. Try using emitterPosition instead (and so you'll have to provide CGPoint values wrapped up in an NSValue).

I just tried this on my own machine and it works fine:

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"emitterPosition"];
ba.fromValue = [NSValue valueWithCGPoint:CGPointMake(30,100)];
ba.toValue = [NSValue valueWithCGPoint:CGPointMake(200,100)];
ba.duration = 6;
ba.autoreverses = YES;
ba.repeatCount = HUGE_VALF;
[emit addAnimation:ba forKey:nil];

Other things to think about:

  • You can typically use nil as the key in addAnimation:forKey:, unless you're going to need to find this animation later (e.g. to remove it or override it in some way). The key path is the important thing.

  • Setting removedOnCompletion to NO is almost always wrong and is typically the last refuge of a scoundrel (i.e. due to not understanding how animation works).

  • If, as you say, setting _emitterLayer.emitterPosition = newEmitterPosition inside the completion block does animate, then when why are you using CABasicAnimation at all? Why not just call UIView animate... and set the emitterPosition in the animations block? If that works, it will kill two birds with one stone, moving the position and animating it too.

于 2012-11-30T21:41:22.453 に答える