58

Is there any way to tell if a UIView is in the middle of an animation? When I print out the view object while it is moving I notice that there is an "animations" entry:

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>>

When the animation has stopped and I print the view, the "animations" entry is now gone:

search bar should end editing: <UISearchBar: 0x2e6240; frame = (0 0; 320 88); text = ''; autoresize = W+BM; layer = <CALayer: 0x2e6e00>>
jscs
  • 63,694
  • 13
  • 151
  • 195
prostock
  • 9,327
  • 19
  • 70
  • 118

10 Answers10

43

A UIView has a layer (CALayer). You can send animationKeys to it, which will give you an array of keys which identify the animations attached to the layer. I suppose that if there are any entries, the animation(s) are running. If you want to dig even deeper have a look at the CAMediaTiming protocol which CALayer adopts. It does some more information on the current animation.

Important: If you add an animation with a nil key ([layer addAnimation:animation forKey:nil]), animationKeys returns nil.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Nick Weaver
  • 47,228
  • 12
  • 98
  • 108
  • @Martin sorry I can't tell, back then when I gave the answer it worked. Maybe something changed since iOS 4.x? – Nick Weaver Feb 10 '12 at 12:55
  • 1
    @Martin it does not work, if you fired animation with nil key. animationKeys is not array of animations, but array of their keys. – Vilém Kurz Jan 18 '13 at 13:37
  • Implemented with swift 3 iOS 10 and this works for me when checking if a custom animation is finished when popping a view controller from the stack. Just make sure to set animation with a key, and not nil. `self?.view.layer.add(transition, forKey: "fade_animation")` – bmjohns Sep 05 '17 at 17:17
  • I did: `let alreadyAnimated = imageView.layer.animationKeys()?.isEmpty ?? true` – Nike Kov Mar 20 '19 at 13:12
30

iOS 9+ method, works even when layer.animationKeys contains no keys:

let isInTheMiddleOfAnimation = UIView.inheritedAnimationDuration > 0

From the docs:

This method only returns a non-zero value if called within a UIView animation block.

deej
  • 1,703
  • 1
  • 24
  • 26
25

Animations are attached in fact to the underlying Core Animation CALayer class

So I think you can just check myView.layer.animationKeys

Vincent Guerci
  • 14,379
  • 4
  • 50
  • 56
  • animationKeys is a method. So you must call [myView.layer animationKeys] – Martin Feb 10 '12 at 10:00
  • 14
    animationKeys is a message, like any property accessor is... dot or brackets, just a matter of *style*. – Vincent Guerci Feb 10 '12 at 10:13
  • But animationKeys is *not* a property ! https://developer.apple.com/library/IOs/#documentation/GraphicsImaging/Reference/CALayer_class/Introduction/Introduction.html (of course, it's not very important :) ) – Martin Feb 13 '12 at 08:52
  • 9
    properties are just semantics, everything end up as messages :) it really doesn't matter and compiler/static analysis does not care. As said, just a matter of style, I even use stuff like `UIView.class` or `UIColor.redColor` ... mostly because one dot is more readable and faster to type than 2 brackets and a space to my taste. – Vincent Guerci Feb 13 '12 at 09:38
  • Really ? But compiler does not like it. I didn't try the execution, but it produce a warning with de dotted syntax. – Martin Feb 13 '12 at 09:49
  • 1
    there are cases when using that *trick* is rejected by compiler, like I think mixing static and non static methods: `MySincletonClass.sharedInstance.instanceMethod`. But i just checked, and `myView.layer.animationKeys` doesn't raise any warning / errors for me... – Vincent Guerci Feb 13 '12 at 10:06
  • Works for me - i just check to make sure its nil and if so i animate. – Jeef Mar 07 '16 at 13:34
  • IMHO code readability is more important than whether it compiles or not – Niklas Berglund Jul 18 '16 at 07:21
11

I'm not sure of the context of the question but I had was attempting to find out if a view was animating before starting a second animation to avoid skipping. However there is a UIView animation option UIViewAnimationOptionBeginFromCurrentState that will combine the animations if necessary to give a smooth appearance. Thereby eliminating my need to know if the view was animating.

Ryan Poolos
  • 18,421
  • 4
  • 65
  • 98
11

There are a lot of out-of-date answers here. I just needed to prevent a UIView animation being started if there was one running on the same view, so I created a category on UIView:-

extension UIView {

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

}

This way I can just check any view's animation status like this:-

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

This seems less fragile than keeping track of it with a state variable.

Echelon
  • 7,306
  • 1
  • 36
  • 34
  • 1
    Sometimes you want to know exactly which property is animating (so as not to animate it again, particularly important with transforms). `func hasAnimation(_ key:String) -> Bool { guard let keys = layer.animationKeys() else { return false } return keys.contains(key) }` – David James Oct 09 '20 at 14:58
5

There is a hitch with the animationKeys trick.

Sometimes there could be a few animationKeys lingering after an animation is completed.

This means that a non-animating layer could return a set of animationKeys even if it isn't actually animating.

You can make sure that animationKeys are automatically removed by setting an animation's removedOnCompletion property to YES.

e.g.

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

If you do this for all the animations you apply to your layer, it will ensure that when the layer isn't animating there are no animationKeys present.

C4 - Travis
  • 4,502
  • 4
  • 31
  • 56
3

Some of these didn't work for me. The reason for that is that these animations are asynchronous.

What I did is defined a property

@property BOOL viewIsAnimating;

And in my animation

[UIView animateWithDuration:0.25
                 animations:^{
                     viewIsAnimating = YES;
                 } completion:^(BOOL finished) {
                     if (finished) {
                         viewIsAnimating = NO;
                     }
                 }];
MendyK
  • 1,643
  • 1
  • 17
  • 30
1

Ref to the question: UIView center position during animation

I compare the view's frame and layer.presentation()?.frame to check it is animating. If leftView is on the way to finish, the leftView.layer.presentation()?.frame does not equal to its frame:

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

But this method may not work if the view move to the end position during the animation. More condition check may be necessary.

Bill Chan
  • 3,199
  • 36
  • 32
0

You could query the presentation layer as suggested here My presentation layer does not match my model layer even though I have no animations

Community
  • 1
  • 1
HeikoG
  • 1,793
  • 1
  • 20
  • 29
0

You can use the layer property of a UIView. CALayer has a property called animation keys, you can check its count if it is greater than 0.

if (view.layer.animationKeys.count) {

  // Animating

}else {

// No

}

In the Documentation:

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

Returns an array containing the keys of all animations currently * attached to the receiver. The order of the array matches the order * in which animations will be applied.

vrat2801
  • 538
  • 2
  • 13