Paul Morris
Paul Morris

Reputation: 1773

Best way to handle a chain of animations with UIViewAnimations

I am build an iOS application in which I have a requirement for the backgroundColor of the view to animation through a selection of colors. In my viewDidLoad method I am basically firing off a chain of UIView animations using the following.

-(void) beginFirstStageBackgroundAnimation
{
    [UIView animateWithDuration: 3.0f
                          delay: 3.0f
                        options: UIViewAnimationOptionTransitionCrossDissolve
                     animations:^{
                         self.view.backgroundColor = [UIColor colorWithRed: 251./255 green:203./255 blue:147./255 alpha:1.0];
                     }
                     completion:^(BOOL finished){
                         [self secondStageBackgroundAnimation];
                     }
     ];
}

The completion handler fires the second method containing the next color....

-(void) secondStageBackgroundAnimation
{
    [UIView animateWithDuration: 3.0f
                          delay: 3.0f
                        options: UIViewAnimationOptionTransitionCrossDissolve
                     animations:^{
                         self.view.backgroundColor = [UIColor colorWithRed:251./255 green:224./255 blue:96./255 alpha:1.0];
                     }
                     completion:^(BOOL finished){
                         [self thirdStageBackgroundAnimation];
                     }
     ];
}

And so on...this goes on until in the seventh animation I call [self beginFirstStageBackgroundAnimation]; in the completion to begin the process again.

When the I leave the view with the back button, in viewDidDisappear or when the timer on the display ends I run [self.view.layer removeAllAnimations]; to entirely get rid of the animations. However, the app runs and works exactly as intended but seems to be crashing after leaving the view and running for a whole and although I don't know why yet it seems related to this animation chain.

Is there a better solution to animate the backgroundColor of the view every 3 seconds with a 3 second delay?

And does [self.view.layer removeAllAnimations]; actually do what I want?

EDIT: The code where I leave the view is as follows:

-(void) backBtnPressed:(id)sender
{
    [self stopAudio]; // stop audio that is playing.
    [self.view.layer removeAllAnimations]; // remove all animations?
    [self.navigationController popViewControllerAnimated: YES]; // pop view from stack
}

Upvotes: 8

Views: 3783

Answers (3)

danh
danh

Reputation: 62676

I think the way to go is to represent what you want to happen in each animation parametrically (which in this case is just a color), and then keep those params in an array. The array is like your animation to-do list:

@property(strong, nonatomic) NSArray *animationTodo;

We also need to keep track of where we are in the list. Use a boxed (NS) number so we can represent "don't animate" as a nil, distinct from index zero:

@property(strong, nonatomic) NSNumber *animationIndex;

The array is initialized with colors:

self.animationTodo = [NSArray arrayWithObjects:[UIColor redColor], [UIColor greenColor], ... nil];

Now the animation can be compressed to a single method:

- (void)animate {

    if (!animationIndex) return;
    NSInteger index = [animationIndex intValue];
    [UIView animateWithDuration:3.0 animations:^{
        self.view.backgroundColor = [animationToDo objectAtIndex:index];
    } completion:^(BOOL finished) {
        NSInteger nextIndex = (index==self.animationTodo.count-1)? 0 : index+1;  // advance index in a ring
        self.animationIndex = [NSNumber numberWithInt:nextIndex];
        // schedule the next one in 3s
        [self performSelector:@selector(animate) withObject:nil afterDelay:3.0];
    }];
}

Perform selector let's you state the delay and also has the positive effect of not winding up the stack each time you start the next animation.

Stopping is simple now, too. Do this before the view disappears and any other time you wish to stop.

- (void)stopAnimating {

    self.animationIndex = nil;
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
}

Upvotes: 7

Sergey Kuryanov
Sergey Kuryanov

Reputation: 6124

You can try to use CAAnimation I think it will be easier to you to manage colors and animations

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSArray *colors = @[[UIColor greenColor],
                        [UIColor redColor],
                        [UIColor blueColor],
                        [UIColor yellowColor],
                        [UIColor orangeColor]
                        ];
    NSMutableArray *animations = [NSMutableArray new];
    float colorAnimationDuration = 3.0f;
    for (int i = 0; i < colors.count; i++)
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
        animation.toValue = (id)[[colors objectAtIndex:i] CGColor];
        animation.duration = colorAnimationDuration;
        animation.beginTime = i * colorAnimationDuration;
        animation.removedOnCompletion = NO;
        animation.fillMode = kCAFillModeForwards;

        [animations addObject:animation];
    }

    CAAnimationGroup *animationsGroup = [CAAnimationGroup new];
    animationsGroup.duration = colors.count * colorAnimationDuration;
    animationsGroup.repeatCount = HUGE_VALF;
    animationsGroup.animations = animations;

    [self.view.layer addAnimation:animationsGroup forKey:@"myAnimations"];

}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.view.layer removeAnimationForKey:@"myAnimations"];
}

Upvotes: 5

meronix
meronix

Reputation: 6176

try to add a check inside every animation method, like this:

-(void) secondStageBackgroundAnimation
 {
 if (self.view.superview){
    [UIView animateWithDuration: 3.0f
                          delay: 3.0f
                        options: UIViewAnimationOptionTransitionCrossDissolve
                     animations:^{
                         self.view.backgroundColor = [UIColor colorWithRed:251./255 green:224./255 blue:96./255 alpha:1.0];
                     }
                     completion:^(BOOL finished){
                         [self thirdStageBackgroundAnimation];
                     }
     ];
 }
}

could you post the code where you "leave the view with the back button"? the problem could be there...

Upvotes: -1

Related Questions