iOS - UIView animateWithDuration 中的 completion block 被过早调用

33

我想在表视图单元格被选中时执行一些动画。由于某些原因,完成块(completion block)过早地被调用了。即使将持续时间设置为10秒,完成块也会立即被调用。

[UIView animateWithDuration:10.0 animations:^{    message.frame = newFrame;} completion:^(BOOL finished) {    NSLog(@"DONE???");}];
任何想法为什么会发生这种情况?谢谢。

这段代码在哪里执行?如果是在 init 方法中执行,那就太早了。应该在 viewDidLoad 或类似的方法中,在控件创建完成后执行。您还可以检查 finished 参数。 - Rob
1
完成参数的值是什么?如果它是NO,你的动画可能会被中断。 - Dominic Sander
抱歉回复晚了,但Dominic Sander的想法是正确的。finished的BOOL值记录为“NO”。 - ryan
4个回答

30

根据UIView文档

completion

一个在动画序列结束时被执行的块对象。这个块没有返回值,并且需要一个布尔类型的参数,指示动画是否在调用完成处理程序之前实际完成。如果动画的持续时间为0,则在下一个运行循环周期的开头执行此块。该参数可以为NULL。

这意味着不能保证代码仅在动画完成时执行。我建议您检查“finished”参数作为执行的条件。


1
那么,为了确保我理解正确,您是说即使动画没有被显式地中断,也没有被替换为另一个动画,完成块仍然可能在实际完成之前被调用?或者这两种情况是您所知道的唯一例外吗? - Nate
2
我唯一遇到这个块被过早调用的实际经验是在动画完成之前被中断。 - Stavash
1
我在iOS 9.3上遇到了相反的问题 - 完成块从未被调用!有什么线索吗? - RPM
1
非常好的建议,"检查'finished'参数作为执行条件"。谢谢你。 - Kemal Can Kaynak
2
我有同样的问题。即使动画没有发生,'finished'参数的值仍为true,而且无论我将持续时间设置多长,完成块都会立即调用。 - MikeG
显示剩余2条评论

18

是的,之所以被称为“太早”,是因为它在某种程度上被打断了。可能是由于模态展示过渡或其他一些原因。根据您的需要,以下可能是您喜欢的解决方案。我们通过手动延迟执行动画代码来避免冲突,如下所示:

// To get this in Xcode very easily start typing, "dispatch_aft..."

// Note the "0.2". This ensures the outstanding animation gets completed before we start ours
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [UIView animateWithDuration:1.0 delay:0 options:0 animations:^{
        // Your animation code
    } completion:^(BOOL finished) {
        // Your completion code
    }];
});

3
作为替代方案,您可以始终调用[containingView layoutIfNeeded]来清除动画堆栈,然后调用[UIView animateWith .....]。思考一下。 - BonanzaDriver
不太美观,但这是唯一有效的解决方案,可以在取消转换后进行动画。 - Lucas Eduardo
谢谢,我昨天开始就一直在寻找这个问题的解决方案。 - NDM

12

如果动画没有效果,例如将视图的 alpha 设置为其已经具有的值,则完成可以提前调用。


self.leftViewHeightConstraint.constant = 0 self.leftView.superview?.layoutIfNeeded() print("size (self.tableView.frame.height)") UIView.animate(withDuration: 30, delay: 0.5, options: [.curveEaseOut] , animations: { self.leftViewHeightConstraint.constant = self.tableView.frame.height self.leftView.superview?.layoutIfNeeded() }, completion: {finished in print("anim completed") }) // 它总是进入动画完成 - saurabhgoyal795

1

为了帮助大家,我把这个问题发在这里。我的情况是使用了一个 keyframe 动画,但完成块立即被调用了。请注意以下概念:

  • duration: 动画的总持续时间,以秒为单位。如果您指定负值或0,则更改将立即进行且无需动画。

  • frameStartTime: 指定动画开始的时间。此值必须在0到1的范围内,其中0表示动画的开始,1表示动画的结束。例如,对于持续时间为两秒的动画,指定开始时间为0.5会导致动画在整个动画开始后一秒开始执行。

  • frameDuration: 动画到指定值的持续时间。此值必须在0到1的范围内,并表示相对于整个动画长度的时间量。如果指定值为0,则动画块中设置的任何属性会立即更新到指定的开始时间。如果指定非零值,则属性会在该时间段内进行动画处理。例如,对于持续时间为两秒的动画,指定持续时间为0.5会导致动画持续时间为一秒。

任何一个错误的设置都会干扰完成调用的时间。


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