我有一批动画调用,通过迭代数组调用。所有这些调用都嵌套在一个封装动画块中,以便它们有效地并行执行。我也有一个完成块,我只想在所有嵌套动画完成后触发。
问题是嵌套动画的持续时间是未知的,所以我不能简单地计算哪个调用将是最后完成的,并在此调用上设置完成块。类似地,我不能计算持续时间并在完成块上使用延迟调用。
希望一个例子能让你更清楚。这是我要做的一个(非常简化的)版本:
-(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
}
使用dispatch_groups和异步调用提供的功能,如下所述:
http://developer.apple.com/library/ios/文档/一般/概念/ConcurrencyProgrammingGuide/OperationQueues OperationQueues.html
显然是理想的,然而UIView animateWithDuration调用本身是一个异步调用,因此在dispatch_group_async中调用它将无法正常工作。我知道我可以做一些事情,比如有一个__block count变量在nestedCompletionBlock中递减,作为确定哪一个是最后一个的方式,但是在我拥有的代码中,这是相当混乱的(上面是一个简化的例子)。
有没有好的方法来做这件事?也许同步做animateWithDuration的某种方式,以便它将与dispatch_group_async一起工作?
正在运行iOS 5.0。
更新:
@Rob-
谢谢你的回答!这几乎解决了我的问题——之前没有研究过cattransaction,我想我应该在问之前再深入一点。:)
但是还有一个问题。正如我之前提到的,我的例子被简化了,剩下的问题是动画有嵌套的完成块。对于链接动画),我需要在封闭事务中包含它。
例如,如果我运行以下代码:
-(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];
}
得到以下输出:
2011-12-08 15:11:35.828 testProj[51550:f803]动画第一阶段
2011-12-08 15:11:35.831 testProj[51550:f803]动画第一阶段
2011-12-08 15:11:37.832 testProj[51550:f803]动画第二阶段
2011-12-08 15:11:37.834 testProj[51550:f803]动画第二阶段
2011-12-08 15:11:37.848 testProj[51550:f803]所有动画完成!
2011-12-08 15:11:39.834 testProj[51550:f803]第二阶段完成
2011-12-08 15:11:39.851 testProj[51550:f803]第二阶段完成
而我需要的是"所有动画完成!"事件只在所有内部操作结束后才被触发。
也许与我只在类级别设置完成块的事实有关,因此不能将每个块绑定到特定的操作?
在任何情况下,我仍然没有完全得到它,并使用UIView animateWithDuration重载与它自己的完成块参数只是抛出了同样的问题。
任何想法?
将整个内容包裹在CATransaction
中,并设置事务的completionBlock
。这是我的测试:
static void setX(UIView *view, CGFloat x)
{
CGRect frame = view.frame;
frame.origin.x = x;
view.frame = frame;
}
- (IBAction)startAnimation:(id)sender {
label.text = @"Animation starting!";
setX(redView, 0);
setX(blueView, 0);
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
label.text = @"Animation complete!";
}];
[UIView animateWithDuration:1 animations:^{
setX(redView, 300);
}];
[UIView animateWithDuration:2 animations:^{
setX(blueView, 300);
}];
} [CATransaction commit];
}
你需要添加QuartzCore框架,如果你还没有。
关于你更新的问题:因为你正在从完成块创建新的动画,这些新的动画不会是原始事务的一部分,所以原始事务不会等待它们完成。
这可能是最简单的,只是开始每个后期动画作为原始事务的一部分,通过使用+[UIView animateWithDuration:delay:options:animations:completion:]
与延迟值,在正确的时间开始后期动画。
它可能对你有帮助。
-(void) testAnim2 {
// NSArray* testArray = [NSArray arrayWithObjects:self.redView, self.blueView, nil];
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
NSLog(@"All completion block");
}];
}[CATransaction commit];
NSLog(@"animation 2nd stage");
[UIView animateWithDuration:2 animations:^{
setX(blueView, 150);
}];
}];
NSLog(@"animation 1st stage");
[UIView animateWithDuration:2 animations:^{
setX(redView, 150);
}];
} [CATransaction commit];
}