测试一个UIView是否正在进行动画

58

有没有办法判断一个UIView是否正在进行动画?当我在View对象移动时打印它,我注意到有一个"animations"条目:

search bar should end editing: <UISearchBar: 0x2e6240; frame = (0 0; 320 88); text = ''; autoresize = W+BM; animations = { position=<CABasicAnimation: 0x6a69c40>; bounds=<CABasicAnimation: 0x6a6d4d0>; }; layer = <CALayer: 0x2e6e00>>

当动画停止并打印视图时,“animations”条目现在已经消失:

search bar should end editing: <UISearchBar: 0x2e6240; frame = (0 0; 320 88); text = ''; autoresize = W+BM; layer = <CALayer: 0x2e6e00>>
10个回答

43
一个 UIView 有一个层 (CALayer)。你可以向它发送 animationKeys,它将返回一个键数组,这些键标识附加到层上的动画。如果有任何条目,则表示动画正在运行。如果你想深入了解,请查看 CAMediaTiming 协议,该协议 CALayer 采用。它提供了有关当前动画的更多信息。 重要提示:如果使用 nil 键添加动画 ([layer addAnimation:animation forKey:nil]),则 animationKeys 返回 nil

@Martin 抱歉,我无法确定,当时我回答的时候它是有效的。也许自从 iOS 4.x 以来有些变化了? - Nick Weaver
1
@Martin,如果你使用nil键触发动画,它是不起作用的。animationKeys不是动画数组,而是它们的键的数组。 - Vilém Kurz
使用Swift 3 iOS 10实现,当从堆栈中弹出视图控制器时,可以检查自定义动画是否完成。只需确保使用关键字而不是nil设置动画。self?.view.layer.add(transition, forKey: "fade_animation") - bmjohns
我做了:let alreadyAnimated = imageView.layer.animationKeys()?.isEmpty ?? true - Nike Kov

31

iOS 9+方法,即使layer.animationKeys不包含任何键也能工作:

let isInTheMiddleOfAnimation = UIView.inheritedAnimationDuration > 0

从文档中可以得知:

只有在UIView动画块内部调用该方法才会返回一个非零值。


25

动画实际上是附加在底层的核心动画 CALayer 类上的。

因此,我认为您可以只需检查 myView.layer.animationKeys


14
animationKeys是一条信息,就像任何属性访问器一样,用点号或方括号访问只是样式的问题。 - Vincent Guerci
9
属性仅是语义,最终一切都会变成消息 :) 这并不重要,编译器/静态分析也不会介意。正如所说,这只是一种风格问题,我甚至使用诸如 UIView.classUIColor.redColor 的东西...主要是因为一个点比两个括号和一个空格更易读且键入更快,这是我的品味。 - Vincent Guerci
1
有时编译器会拒绝使用那种“技巧”,比如我认为混合静态和非静态方法:MySincletonClass.sharedInstance.instanceMethod。但是我刚刚检查了一下,对于myView.layer.animationKeys,我没有收到任何警告/错误... - Vincent Guerci
对我来说可以运行 - 我只是检查它是否为空,如果是的话就进行动画。 - Jeef
在我看来,代码的可读性比它是否能编译更重要。 - Niklas Berglund
显示剩余3条评论

12

这里有很多过时的答案。我只需要防止在同一个视图上运行了一个动画时,再次启动 UIView 动画,因此我在 UIView 上创建了一个类别:

extension UIView {

    var isAnimating: Bool {
        return (self.layer.animationKeys()?.count ?? 0) > 0
    }

}

这样我就可以像这样检查任何视图的动画状态:

if !myView.isAnimating {
  UIView.animate(withDuration: 0.4) {
    ...
  }
} else {
  // already animating
}

这种方法似乎比使用状态变量来跟踪它不那么脆弱。


1
有时候你想要确切地知道哪个属性正在进行动画(以免再次进行动画,特别是在变换中非常重要)。func hasAnimation(_ key:String) -> Bool { guard let keys = layer.animationKeys() else { return false } return keys.contains(key) } - David James

11

我不确定问题的背景,但我尝试找出一个视图是否正在进行动画,以避免跳过第二个动画。然而,有一个UIView动画选项UIViewAnimationOptionBeginFromCurrentState,如果必要,将合并动画以给出平滑的外观。从而消除了我需要知道视图是否正在进行动画的需求。


5

使用animationKeys技巧时存在一个问题。

有时在动画完成后可能会有一些animationKeys残留。

这意味着,即使一个非动画层实际上没有进行动画,它也可能返回一组animationKeys。

您可以通过将动画的removedOnCompletion属性设置为YES来确保自动删除animationKeys。

例如:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"aPath"];
animation.removedOnCompletion = YES;

如果您对图层应用了多个动画效果,请确保在图层不进行动画时没有动画键存在。

3

有些动画对我来说不起作用。原因是这些动画是异步的。

我所做的是定义一个属性。

@property BOOL viewIsAnimating;

在我的动画中
[UIView animateWithDuration:0.25
                 animations:^{
                     viewIsAnimating = YES;
                 } completion:^(BOOL finished) {
                     if (finished) {
                         viewIsAnimating = NO;
                     }
                 }];

1
我的UIButtons的animationKeys属性总是返回nil(即使在动画时),所以我也选择了这种方法来完成工作。 - vikzilla
如果我使用.repeat选项会发生什么? - Alessandro Ornano

1

参考问题: UIView动画期间的中心位置

我比较视图的frame和layer.presentation()?.frame来检查它是否正在进行动画。如果leftView正在完成动画,则leftView.layer.presentation()?.frame不等于其frame:

if self.leftView.layer.presentation()?.frame == self.leftView.frame {
   // the animation finished
} else {
   // the animation on the way
}

但是,如果视图在动画期间移动到最终位置,则此方法可能无法起作用。可能需要进行更多的条件检查。

0

根据相关问题,这种方法行不通。 - paulmelnikow

0
你可以使用UIView的layer属性。CALayer有一个叫做动画键的属性,你可以检查它的数量是否大于0。
if (view.layer.animationKeys.count) {

  // Animating

}else {

// No

}

在文档中:

-(nullable NSArray<NSString *> *)animationKeys;

返回一个数组,其中包含当前附加到接收器的所有动画的键。数组的顺序与动画将被应用的顺序相匹配。

你可以将UIView归类,并添加此功能以实现更好的可重用性。 - vrat2801

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