如何为一个嵌套的UIView animateWithDuration调用组调用单个完成块



我有一批动画调用,通过迭代数组调用。所有这些调用都嵌套在一个封装动画块中,以便它们有效地并行执行。我也有一个完成块,我只想在所有嵌套动画完成后触发。
问题是嵌套动画的持续时间是未知的,所以我不能简单地计算哪个调用将是最后完成的,并在此调用上设置完成块。类似地,我不能计算持续时间并在完成块上使用延迟调用。
希望一个例子能让你更清楚。这是我要做的一个(非常简化的)版本:

-(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];
}

最新更新