UIView animateWithDuration completion已完成= YES,尽管已被取消?

10

我正在用户交互中运行此动画,有时在当前动画完成之前可能会再次运行该动画。我希望它能取消之前的动画并继续进行新的动画。

[UIView animateWithDuration:duration
                      delay:0
                    options:UIViewAnimationOptionBeginFromCurrentState
                 animations:^{
                     self.bounds = bounds;
                 }
                 completion:^(BOOL finished) {
                     if (finished) {
                         // Do some cleanup after animating.
                     }
                 }];

视觉上看起来似乎没问题,但在我的完成块中告诉我它在两种情况下都已完成,这导致清除代码过早运行。因此,第一个动画的完成块会在第二个开始后立即运行,且finished=YES。我期望它的finished值为NO,而第二个(一旦完成)则为YES

有没有办法知道动画是否完成或是否被其他方式取消了?

Sidenote: 我尝试使用CABasicAnimation进行相同的动画,然后第一次得到的是finished=NO,第二次得到的是YES,因此我得到的行为似乎是特定于animateWithDuration的。

这里有一个GIF,显示了使用持续时间为10且完成块更新标签的上述代码。如您所见,在每次使用animateWithDuration调用重新启动动画时,finished都为YES

2个回答

13

我进一步调查后发现这是iOS 8特有的一个问题,因为现在动画默认是可叠加的,这意味着动画将继续并相互叠加而不是被取消。

在我的搜索中,我找到了一个名为“Building Interruptible and Responsive Interactions”的WWDC会议,其中谈到了这个问题。在会议上,他们声称动画将同时完成,但在我的测试中并非如此(请参见问题中的GIF)。建议在会议中采用的解决方法是保持计数器记录动画启动次数和完成次数,并在计数器达到零时执行后续动作。这有点低级,但是有效。以下是已根据此进行调整的问题代码:

// Add this property to your class.
self.runningAnimations++;
[UIView animateWithDuration:duration
                      delay:0
                    options:UIViewAnimationOptionBeginFromCurrentState
                 animations:^{
                     self.bounds = bounds;
                 }
                 completion:^(BOOL finished) {
                     if (--self.runningAnimations == 0) {
                         // Do some cleanup after animating.
                     }
                 }];

显然,你需要为每种类型的动画设置一个单独的计数器,这样最终可能会有些混乱,但我认为除非直接使用CAAnimation,否则没有更好的方法。


谢谢,这帮助我修复了由于iOS 9上finished始终为true而导致的动画故障。 - malhal
实际上,更好的方式是检查表示层的边界,这样您就会知道它是否完成。 - malhal
这也是我最终所做的(iOS 9.3,Swift)!感谢您的帮助。 - Nikolay Suvandzhiev

0
你尝试过哪种方法来取消动画?
theView_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[theView_ setBackgroundColor:[UIColor magentaColor]];
[self.view addSubview:theView_];

[UIView animateWithDuration:10
                 animations:^{
                     theView_.frame = CGRectMake(200, 200, 50, 50);
                 } completion:^(BOOL finished) {
                     NSLog(@"done : %@", @(finished));
                     if(finished) {

                     }
                     else {

                     }
                 }];

[self performSelector:@selector(cancelAnimation) withObject:nil afterDelay:2];

cancelAnimation方法如下:

- (void)cancelAnimation {
    NSLog(@"cancel");
    [theView_.layer removeAllAnimations];

}

当动画被取消时,finished将返回false

日志消息如预期所示:

2015-02-23 14:13:58.625 TestPrj[47338:6070317] cancel
2015-02-23 14:13:58.626 TestPrj[47338:6070317] done : 0

1
是的,但是立即调用removeAllAnimations会将视图放入其最终状态。我想通过第二次调用animateWithDuration来取消第一个动画。第二次调用确实取消了它(completion块直接被调用),但finished值为YES而不是NO,因此我无法区分已取消的动画和实际完成的动画。 - Blixt
@Blixt,您想要实现的是使用块动画方法暂停/恢复动画。您看过这个网址吗?https://dev59.com/Rm_Xa4cB1Zd3GeqPwAGc - Ryan
这很巧妙,但实际上我需要替换动画,因为bounds的动画根据用户的输入会转到任意位置。有没有办法替换动画的toValue而不是暂停它? - Blixt
@Blixt 如果你必须使用 UIView animationWith...,我认为没有可能的方法。然而,使用 CABasicAnimation 是可行的,你可以在动画开始时将 fromValue 设置为 toValue - Ryan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接