Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
}
like image 241
Paul Morris Avatar asked Mar 22 '13 16:03

Paul Morris


People also ask

Does UIView animate run on the main thread?

The contents of your block are performed on the main thread regardless of where you call [UIView animateWithDuration:animations:] .

Does UIView animate need weak self?

No, it is not needed in this case. animations and completion are not retained by self so there is no risk of strong retain cycle.

How does UIView animate work?

it queries the views objects for changed state and gets back the initial and target values and hence knows what properties to change and in what range to perform the changes. it calculates the intermediate frames, based on the duration and initial/target values, and fires the animation.


1 Answers

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];
}
like image 135
danh Avatar answered Sep 17 '22 17:09

danh