Chain UIView animations with time intervals

2019-03-19 03:28发布

问题:

I need to animate 3 UIViews (fade in/out). 1 animation duration is 0.6s (fade in/out cycle is 0.6+0.6s). But I need to launch animations in 0.2 seconds.

  • 1st animation should be launched in 0.0 seconds.
  • 2nd animation should be launched in 0.2 seconds.
  • 3rd animation should be launched in 0.4 seconds.

And all of them should be looped "indefinitely" (until some trigger).

What I have at the moment:

- (void)playAnimation {
    isAnimated = YES;
    [self animateView:firstView afterDelay:0.0];
    [self animateView:secondView afterDelay:0.2];
    [self animateView:thirdView afterDelay:0.4];
}

- (void)stopAnimation {
    isAnimated = NO;
}

- (void)animateView:(UIView *)animatedView afterDelay:(float)delay {
    if(isAnimated) {
        [UIView animateWithDuration:0.6 delay:delay options:UIViewAnimationOptionTransitionNone
                     animations:^ {
            animatedView.alpha = 1.0;
        } completion:^(BOOL finished) {
            [UIView animateWithDuration:0.6 animations:^ {
                animatedView.alpha = 0.0;
            } completion:^(BOOL finished) {
                [self animateView:animatedView afterDelay:0.0];
            }];
        }];
    }
}

This code works unpredictable. Sometimes view animation works like I want (with phase 0.2 seconds), some times it starts in the same time... What will be the proper way to do that? I've also tried to remove afterDelay: part from method signature and launch animateView method like that with exactly same effect:

[self performSelector:@selector(animateView:) withObject:thirdView afterDelay:0.6];

UPDATE
I've noticed that animation "breaks" when heavy networking stuff is performing in background (loading big images using AFNetworking). I don't mind if animation will "freeze" a bit (though I prefer to not have delays at all) but I really want to keep phases of all animations linked (with same phase difference).

To make problem easier to understand I've added graphs. Y is alpha, X is time. Top 3 graphs - what I want to have. Bottom ones - what I currently have. Highlighted area is where problem comes. You can see that second view's animation freeze for 0.2 seconds and synchronise with 3rd one. So they start blinking in the same phase. This is just one example. Some times they can animate ok, sometimes all 3 views "syncronize" in few rounds of animation and blink in same phase...

回答1:

Looks like you want the same animation, applied to all 3 views, offset by t=0.2. You can use Core Animation to do exactly what you want with very little effort.

Doing it this way they will always be timed correctly.

I propose this:

-(void)playAnimation
{
    CABasicAnimation * anim = [ CABasicAnimation animationWithKeyPath:@"opacity" ] ;
    anim.autoreverses = YES ;
    anim.repeatCount = CGFLOAT_MAX ;
    anim.removedOnCompletion = NO ;
    anim.duration = 0.6 ;
    anim.fromValue = @0.0 ;
    anim.toValue = @1.0;

    // finish configuring your animation here (timing function, speed, duration, fill mode, etc) ...

    CFTimeInterval t = CACurrentMediaTime() ;

    anim.beginTime = t ;
    [ self.firstView.layer addAnimation:anim forKey:@"opacity-anim" ] ; // name is so you can remove this anim later

    anim.beginTime += 0.2 ;
    [ self.secondView.layer addAnimation:anim forKey:@"opacity-anim" ] ;

    anim.beginTime += 0.2 ;
    [ self.thirdView.layer addAnimation:anim forKey:@"opacity-anim" ] ; // name is so you can remove this anim later
}

-(void)stopAnimation
{
    [ self.firstView.layer removeAnimationForKey:@"opacity-anim" ] ;
    [ self.secondView.layer removeAnimationForKey:@"opacity-anim" ] ;
    [ self.thirdView.layer removeAnimationForKey:@"opacity-anim" ] ;
}

edit: oops! forgot the start, end values!



回答2:

The way to schedule animations properly is by using the CAMediaTiming protocol that the CAKeyframeAnimation class conforms to. See my answer below for links to resources on how to achieve this.

Changing speed of an ongoing CAKeyframeAnimation animation



回答3:

Try instead of performSelector: the following sample

- (void)animateView:(UIView *)animatedView afterDelay:(float)delay {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        if(isAnimated) {
            [UIView animateWithDuration:0.6 delay:0.0 options:UIViewAnimationOptionTransitionNone
                         animations:^ {
                animatedView.alpha = 1.0;
            } completion:^(BOOL finished) {
                [UIView animateWithDuration:0.6 animations:^ {
                    animatedView.alpha = 0.0;
                } completion:^(BOOL finished) {
                    [self animateView:animatedView afterDelay:0.0];
                }];
            }];
        }
    });
}


回答4:

Hope it will work as what you want.

- (void)animateView:(UIView *)animatedView afterDelay:(float)delay
{
    if(isAnimated) {
        [UIView animateWithDuration:0.6 delay:delay options:UIViewAnimationOptionTransitionNone
                         animations:^ {
                             animatedView.alpha = 1.0;
                         } completion:^(BOOL finished) {
                             [UIView animateWithDuration:0.6 delay:delay options:UIViewAnimationOptionTransitionNone animations:^{
                                  animatedView.alpha = 0.0;
                             } completion:^(BOOL finished) {
                                 [self animateView:animatedView afterDelay:delay];
                             }];
                         }];
    }
}

This is just some modification of your code.



回答5:

Try this, this might work for you.

- (void)playAnimation {
    isAnimated = YES;

    [self performSelector:@selector(animateView:) withObject:firstView afterDelay:0.1];
    [self performSelector:@selector(animateView:) withObject:secondView afterDelay:0.2];
    [self performSelector:@selector(animateView:) withObject:thirdView afterDelay:0.4];
}


- (void)animateView:(UIView *)animatedView {
   //Here "animatedView" will contain (firstView/secondView/thirdView) whatever you are passing
   //your animation
}