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

发布于 2024-12-20 02:26:47 字数 3415 浏览 2 评论 0原文

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

-(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/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

显然是理想的,但是 UIView animateWithDuration call 本身就是一个异步调用,因此在dispatch_group_async 中调用它将无法正常工作。
我知道我可以做一些事情,比如拥有一个 __block 计数变量,该变量在nestedCompletionBlock 中递减,作为确定哪个是最后一个的方法,但在我的代码中,这非常混乱(上面是一个简化的示例)。
有什么好的方法吗?也许有某种同步执行 animateWithDuration 的方法,以便它可以与dispatch_group_async一起使用?

在 iOS 5.0 上工作。

更新:

@Rob-
谢谢您的回答!这几乎解决了我的问题 - 之前没有研究过 CATransaction,我想我应该在询问之前深入挖掘一下。 :)
但是仍然存在一个问题。正如我之前提到的,我的示例被简化了,剩下的问题是由于动画具有嵌套的完成块(即链式动画),我需要将其包含在封闭的事务中。
例如,如果我运行以下代码:

-(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 重载及其自己的完成块参数只会引发同样的问题。
有什么想法吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

屌丝范 2024-12-27 02:26:47

将整个事情包装在 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:] 来启动每个后续动画作为原始事务的一部分,并使用延迟值启动后续动画正确的时间。

Wrap the whole thing in a CATransaction, and set the transaction's completionBlock. This was my test:

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

You'll need to add the QuartzCore framework if you haven't already.

Regarding your updated question: since you're creating new animations from completion blocks, those new animations won't be part of the original transaction, so the original transaction won't wait for them to finish.

It would probably be simplest to just start each later animation as part of the original transaction by using +[UIView animateWithDuration:delay:options:animations:completion:] with a delay value that starts the later animation at the right time.

等风也等你 2024-12-27 02:26:47

它可能对你有帮助。

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

It may help you.

-(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];
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文