Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do 'serial' animation with GCD?

I'm trying to make a custom UIView displays on screen for 5s when a remote notification comes.

Code like this:

//customView.alpha = 1.0 here
[UIView animateWithDuration:1 animations:^{
                                  customView.alpha = 0.3;
                              } 
                              completion:^(BOOL finished){
                                  // remove customView from super view.
                              }];

Problem and What I Need

But there're cases that a couple of notification may come at short time interval, in which several customView may be animating at the same time and one may cover others.

I want these animations perform one after another, so that they won't conflict.

Assumed but Failed

//(dispatch_queue_t)queue was created in other parts of the code
dispatch_sync(queue, ^{
    [UIView animationWithDuration:animations:...];
});

After making the animation in GCD queue, I got the same result as the origin code I used, which didn't use GCD. Animations are still conflicting.

BTW, I heard that animations or tasks involving UI should always be run on main thread, but in my second code the animation seemed done smoothy. Why?

like image 451
studyro Avatar asked May 30 '12 13:05

studyro


Video Answer


2 Answers

If it's the same animation that runs every time then you could just have store the number of times that the animation should run (not the same as the repeat count property of the animation).

When you receive the remote notification you increment the counter and call the method that animates if the counter is exactly one. Then in the methodThatAnimates you recursively call yourself in the completion block while decreasing the counter every time. It would look something like this (with pseudocode method names):

- (void)methodThatIsRunWhenTheNotificationIsReceived {
    // Do other stuff here I assume...
    self.numberOfTimesToRunAnimation = self.numberOfTimesToRunAnimation + 1;
    if ([self.numberOfTimesToRunAnimation == 1]) {
        [self methodThatAnimates];
    }
}

- (void)methodThatAnimates {
    if (self.numberOfTimesToRunAnimation > 0) {
        // Animation preparations ...
        [UIView animateWithDuration:1 
                         animations:^{
                                  customView.alpha = 0.3;
                         } 
                         completion:^(BOOL finished){
                                  // Animation clean up ...
                                  self.numberOfTimesToRunAnimation = self.numberOfTimesToRunAnimation - 1;
                                  [self methodThatAnimates];
                         }];
    }
}
like image 182
David Rönnqvist Avatar answered Sep 21 '22 05:09

David Rönnqvist


Using queues to submit animations in sequence won't work, because the method that begins the animation returns immediately, and the animation is added to the animation tree to be performed later. Each entry in your queue will complete in a tiny fraction of a second.

If each of your animations operates on the same view then by default the system should let each animation finish running before it starts the next one.

To quote the docs for the UIViewAnimationOptionBeginFromCurrentState options value:

UIViewAnimationOptionBeginFromCurrentState

Start the animation from the current setting associated with an already in-flight animation. If this key is not present, any in-flight animations are allowed to finish before the new animation is started. If another animation is not in flight, this key has no effect.

If you want to chain a series of animations, here's what I would do:

Create a mutable array of animation blocks. (code blocks are objects, and can be added to an array.) Write a method that pulls the top animation block off the array (and removes it from the array) and submits it using animateWithDuration:animations:completion, where the completion method simply invokes the method again. Make the code assert a lock before pulling an item off the array, and free the lock after deleting the item.

Then you can write code that responds to an incoming notification by asserting your animation array lock, adding an animation block to the lock, and releasing the lock.

like image 29
Duncan C Avatar answered Sep 18 '22 05:09

Duncan C