I have a batch of animation calls, invoked by iterating through an array. All these calls are nested within an encapsulating animation block so that they execute effectively in parallel. I also have a completion block with I only want to fire once all of the nested animations have completed.
The problem is that the nested animations are of unknown durations, so I cannot simply calculate which call will be the last to finish and set the completion block on this call. Similarly I cannot calculate the duration and use a delayed invocation on the completion block.
Hopefully an example will make this clearer. This is a (very simplified) version of what I'm trying to do:
-(void) animateStuff:(CGFloat)animationDuration withCompletionBlock:(void) (^)(BOOL)completionBlock {
// encapsulating animation block so that all nested animations start at the same time
[UIView animateWithDuration:animationDuration animations:^{
for(MyObject* object in self.array) {
// this method contains other [UIView animateWithDuration calls...
[self animationOfUnknownDurationWithObject:object nestedCompletionBlock:^(BOOL finished) {
// what I effectively what here is:
if(last animation to finish) {
completionBlock(YES);
}
}];
}
}]; // cannot use the completion block for the encapsulating animation block, as it is called straight away
}
The functionality provided by using a dispatch_groups and asynchronous invocation as described here:
http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
would obviously be ideal, however the UIView animateWithDuration call is an asynchronous call in itself and so invoking it within dispatch_group_async will not work properly.
I know I could do something like having a __block count variable that gets decremented within nestedCompletionBlock as a way of determining which is the final one, but in the code that I have this is pretty messy (the above is a simplified example).
Is there a good way of doing this? Perhaps some way of doing animateWithDuration synchronously, so that it will work with dispatch_group_async?
Working on iOS 5.0.
UPDATE:
@Rob-
Thankyou for your answer! This nearly solves my problem - hadn't looked into CATransaction before, guess I should have dug a little deeper before asking. :)
However one issue remains. As I mentioned before, my example was simplified, and the remaining problem arises from the fact that the animations have nested completion blocks (ie. for chained animations) which I need to include within the enclosing transaction.
So for example if I run the following code:
-(void) testAnim {
NSArray* testArray = [NSArray arrayWithObjects:self.redView, self.blueView, nil];
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
NSLog(@"All animations complete!");
}];
for(UIView* view in testArray) {
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
NSLog(@"2nd stage complete");
}];
NSLog(@"Animation 2nd stage");
[UIView animateWithDuration:2 animations:^{
setX(view, 100);
}];
} [CATransaction commit];
}];
NSLog(@"animation 1st stage");
[UIView animateWithDuration:2 animations:^{
setX(view, 150);
}];
}[CATransaction commit];
}
} [CATransaction commit];
}
I get the following output:
2011-12-08 15:11:35.828 testProj[51550:f803] animation 1st stage
2011-12-08 15:11:35.831 testProj[51550:f803] animation 1st stage
2011-12-08 15:11:37.832 testProj[51550:f803] Animation 2nd stage
2011-12-08 15:11:37.834 testProj[51550:f803] Animation 2nd stage
2011-12-08 15:11:37.848 testProj[51550:f803] All animations complete!
2011-12-08 15:11:39.834 testProj[51550:f803] 2nd stage complete
2011-12-08 15:11:39.851 testProj[51550:f803] 2nd stage complete
Whereas what I need is the "All animations complete!" event to be fired only once all the inner operations have ended.
Perhaps something to do with the fact that I'm only setting completion blocks at the class level and thereby can't tie each block to a specific operation?
In any case I'm still not quite getting it, and using the UIView animateWithDuration overload with it's own completion block argument just throws up the same problem.
Any ideas?