6

I have two rotation animations in my CAAnimationGroup, one that starts from zero and another that repeats and autoreverses from that state:

- (void)addWobbleAnimationToView:(UIView *)view amount:(float)amount speed:(float)speed
{
    NSMutableArray *anims = [NSMutableArray array];

    // initial wobble
    CABasicAnimation *startWobble = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    startWobble.toValue = [NSNumber numberWithFloat:-amount];
    startWobble.duration = speed/2.0;
    startWobble.beginTime = 0;
    startWobble.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [anims addObject:startWobble];

    // rest of wobble
    CABasicAnimation *wobbleAnim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    wobbleAnim.fromValue = [NSNumber numberWithFloat:-amount];
    wobbleAnim.toValue = [NSNumber numberWithFloat:amount];
    wobbleAnim.duration = speed;
    wobbleAnim.beginTime = speed/2.0;
    wobbleAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    wobbleAnim.autoreverses = YES;
    wobbleAnim.repeatCount = INFINITY;
    [anims addObject:wobbleAnim];

    CAAnimationGroup *wobbleGroup = [CAAnimationGroup animation];
    wobbleGroup.duration = DBL_MAX; // this stops it from working
    wobbleGroup.animations = anims;

    [view.layer addAnimation:wobbleGroup forKey:@"wobble"];
}

Since CFTimeInterval is defined as a double, I try setting the duration of the animation group to DBL_MAX, but that stops the animation group from running. However, If I set it to a large number, such as 10000, it runs fine. What is the largest number I can use for a duration of a CAAnimationGroup, to ensure it runs for as near to infinity as possible?

UPDATE: It appears that if I put in a very large value such as DBL_MAX / 4.0 then it freezes for a second, then starts animating. If I put in the value DBL_MAX / 20.0 then the freeze at the beginning is a lot smaller. It seems that having such a large value for the duration is causing it to freeze up. Is there a better way of doing this other than using a very large value for the duration?

Mazyod
  • 21,361
  • 9
  • 86
  • 147
jowie
  • 7,877
  • 8
  • 52
  • 92
  • You're trying to make this group of animations repeat forever? – jscs Mar 27 '13 at 17:19
  • yes, but I only want the second part of the group to repeat forever. – jowie Mar 27 '13 at 17:22
  • Are you sure you need to set the duration at all? What happens if you leave it unset? – jscs Mar 27 '13 at 17:25
  • if I leave it unset, the animation doesn't run at all. Also, the way I have it set up, if the app enters the background and then is reopened, the animation stops, which is also a problem. – jowie Mar 27 '13 at 17:33
  • 1
    Um, `DBL_MAX` represents something on the order of 10^300 years. `10000` represents over 2 hours. How long do you expect your app to run before terminating? – Brian Nickel Aug 02 '13 at 20:19

2 Answers2

1

I am faced with the exact same issue right now... I hope someone proves me wrong, but the only reasonable way I see to handle this situation is by moving the first animation to a CATransaction, and chaining that with the autoreverse animation using:

[CATransaction setCompletionBlock:block];

It's not ideal, but gets the job done.

Regarding your question about the animations being paused when coming back from background, that's a classic limitation of the CoreAnimation framework, many solutions have been proposed for it. The way I solve it is by simply reseting the animations at a random point of the animation, by randomizing the timeOffset property. The user can't tell exactly what the animation state should be, since the app was in the background. Here is some code that could help (look for the //RANDOMIZE!! comment):

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(startAnimating)
                                             name:UIApplicationWillEnterForegroundNotification
                                           object:[UIApplication sharedApplication]];

...

for (CALayer* layer in _layers) 
{
    // RANDOMIZE!!!
    int index = arc4random()%[levels count];
    int level = ...;
    CGFloat xOffset = ...;

    layer.position = CGPointMake(xOffset, self.bounds.size.height/5.0f + yOffset * level);

    CGFloat speed = (1.5f + (arc4random() % 40)/10.f);
    CGFloat duration = (int)((self.bounds.size.width - xOffset)/speed);

    NSString* keyPath = @"position.x";
    CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:keyPath];

    anim.fromValue      = @(xOffset);
    anim.toValue        = @(self.bounds.size.width);
    anim.duration       = duration;
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    // RANDOMIZE!!
    anim.timeOffset     = arc4random() % (int) duration;
    anim.repeatCount    = INFINITY;

    [layer removeAnimationForKey:keyPath];
    [layer addAnimation:anim forKey:keyPath];
    [_animatingLayers addObject:layer];
}
Mazyod
  • 21,361
  • 9
  • 86
  • 147
0

It is much simpler to use a single keyframe animation instead of a group of two separate animations.

- (void)addWobbleAnimationToView:(UIView *)view amount:(CGFloat)amount speed:(NSTimeInterval)speed {
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
    animation.duration = 2 * speed;
    animation.values = @[ @0.0f, @(-amount), @0.0f, @(amount), @0.0f ];
    animation.keyTimes = @[ @0.0, @0.25, @0.5, @0.75, @1.0 ];
    CAMediaTimingFunction *easeOut =[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    CAMediaTimingFunction *easeIn =[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    animation.timingFunctions = @[ easeOut, easeIn, easeOut, easeIn ];
    animation.repeatCount = HUGE_VALF;
    [view.layer addAnimation:animation forKey:animation.keyPath];
}
rob mayoff
  • 342,380
  • 53
  • 730
  • 766