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...
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!
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
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];
}];
}];
}
});
}
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.
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
}