4

Let's say that I make an animation like so:

[UIView animateWithDuration:0.5
                      delay:0.0
                    options:UIViewAnimationOptionCurveEaseInOut
                 animations:^{
                               someView.frame = CGRect(0, 100, 200, 200);
                             }
                 completion:nil];

And Let's say that I have subviews inside someView that have their layoutSubviews method overridden. I would like to identify an ongoing animation inside that layoutSubviews and make corresponding actions depending if the animation I detected was created by me, or if it is for example an animation caused by device rotation.

To get the animations in progress I use the following code:

for (NSString *animationKey in [self.layer animationKeys])
{
    CAAnimation *animation = [self.layer animationForKey:animationKey];
    // ... Do something ...
}

Now If I would be using CABasicAnimation I could set arbitrary properties using:

[myAnimation setValue:@"mySuperCoolAnimation" forKey:@"AnimationName"];

And then check that key on the other side. But I would like to avoid using CABasicAnimation because of my complex view hierarchy.

So for simplicity sake is there a way to do something like that using UIView animateWithDuration:

Ivan Kovacevic
  • 1,322
  • 12
  • 30
  • 1
    Could you just raise a flag to say your animation is in progress, and lower it in the completion block? Or use the current CATransaction to store an arbitrary key, as you would have done with the CABasicAnimation? – matt Mar 23 '13 at 04:15
  • It could work with a flag, but since I have many subviews in place, I would probably have to implement a reference back to the view controller. Where then there would be an array containing info about ongoing animations(I have different animations that need different actions to be triggered in subviews). Anyhow I tried now using setValue:forKey: on CATransaction and it works as expected! So for me that seems like a good and simple solution. I would like to accept it as the correct answer so when you can, write a small answer with an explanation and maybe some code snippet, so I can accept it. – Ivan Kovacevic Mar 23 '13 at 12:46
  • Interesting. You add an object to the current CATransaction inside your animateWithDuration block? Then you interrogate the "in flight" animations looking for one that has that key/value pair? Cool trick. Can you post your code? – Duncan C Mar 23 '13 at 15:20
  • Actually its simpler than that. As far as I figured an CATransaction is automatically created and committed when your current method ends(Docs say: "..when the thread's run-loop next iterates"). So (in my experience) you can even set it outside animateWithDuration block. Like so: [CATransaction setValue:@"mySuperCoolAnimation" forKey:@"AnimationInProgress"]; and then for example in my custom UIView's method layoutSubviews I can get that value with [CATransaction valueForKey:@"AnimationInProgress"]; So it is not that CATransaction applies(setts) that key/value to all existing ongoing animations – Ivan Kovacevic Mar 23 '13 at 16:01
  • The completion block don't add any relevant information if your animation have the option `.repeat`, so it's not a good way to solve this issue. – Alessandro Ornano Oct 05 '17 at 06:48

1 Answers1

3

Could you just raise a flag to say your animation is in progress, and lower it in the completion block?

Or use the current CATransaction to store an arbitrary key, as you would have done with the CABasicAnimation? It is a little-known fact that there is always a CATransaction, which automatically commits and runs the animations at the end of the run loop (after your own code finishes), and then optionally fires a completion handler when it finishes - that, in fact, is how animation works, as I explain in my book:

http://www.apeth.com/iOSBook/ch17.html#_animation_transactions

(See esp. "The Truth About Transactions", further down that page.)

This is cool for various reasons:

  • The current CATransaction is available from your code, even when your code has nothing to do with animation at that moment.

  • You can make your own transaction, wrapping your animations, and commit it (this might be the solution you're after; I can't quite tell)

  • CATransaction has a completion handler. I often use this trick to do something that must follow on right after one of my animations.

  • CATransaction fires its completion handler after its animations finish, even if they are not your animations; I use this trick as a specialized form of delayed performance, to wait until one of the runtime's own internally generated animations ends.

  • CATransaction can store an arbitrary key/value pair, just like CALayer and CAAnimation. I often use this as a way of passing a signal through the transaction, to be picked up when the completion handler runs.

Sorry not to propose any specific solution here, but I'm just suggesting that knowing about CATransaction might allow you to work out something appropriate to your case.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Btw, despite all those cool features of CATransaction I eventually ended up declaring a delegate property in my views and a protocol containing methods and properties so that views can "query" their view controller(delegate) about the status of ongoing animations. I then set the necessary information in the beginning of animations block and change/remove it in the completion block. I think this solution is also much easier to follow(understand) when you browse through the code. So that is basically what you have suggested as a flag-solution in the first place... – Ivan Kovacevic Mar 23 '13 at 17:13
  • Cool but I hope you didn't use a delegate just so a view could see its view controller. You can always reach the view controller from a view by walking the responder chain (`nextResponder`). – matt Mar 23 '13 at 17:17
  • Well I actually did. I was under impression that was bad practice(walking the responder chain)? That it breaks the MVC pattern etc, etc.. "view shouldn't know about its view controller...". Regardless isn't actually creating the delegate property a cleaner solution(less code)? You made it sound like it is a bad thing to do, so now you got me thinking... – Ivan Kovacevic Mar 23 '13 at 17:34
  • Gosh, I have a category on UIView so that every UIView has a method where we walk the superview/responder chain until we reach a UIViewController. I don't see what philosophy that violates; they already *have* this relationship, so why not use it? – matt Mar 23 '13 at 17:59
  • Probably like this: http://stackoverflow.com/a/2596519/1725693 ? But than you still have to cast that reference you get, into one which conforms to your protocol or your exact custom UIViewController type, to avoid compiler warnings when you try to call methods on it which you implemented? Which is why I see the delegate property as a cleaner solution? Is there maybe any downside I'm missing, when implementing a delegate property? – Ivan Kovacevic Mar 23 '13 at 18:10
  • You typically have to cast when you use *any* built-in relationship. I don't see that as a problem. I'm not saying delegate is wrong, it just seems like a boatload of extra work. – matt Mar 23 '13 at 18:15
  • So you would rather do something like this: CGFloat animDuration = [(UIViewController*)[self viewController] animationInProgressDuration]; instead of CGFloat animDuration = [self.delegate animationInProgressDuration]; ... or even just using self.delegate.animationInProgressDuration in place where needed – Ivan Kovacevic Mar 23 '13 at 19:04