33

I'm trying to do some animation when a table view cell gets selected. For some reason, the completion block is getting called way too early. Even setting the duration to 10 seconds, the completion block gets called immediately.

[UIView animateWithDuration:10.0 animations:^{
    message.frame = newFrame;
} completion:^(BOOL finished) {
    NSLog(@"DONE???");
}];

Any thoughts on why this is happening? Thanks.

ryan
  • 1,324
  • 2
  • 18
  • 29
  • Where is this code being executed? If in an `init` method, for example, that's too soon. It should be done in `viewDidLoad` or something like that, after the controls have been created. You can also check the `finished` parameter. – Rob Feb 03 '13 at 05:47
  • 1
    Which value has the finished-Parameter? If it is NO, your animation might be interrupted. – Dominic Sander Feb 03 '13 at 07:53
  • Sorry for the late response but Dominic Sander has the right idea. The BOOL value of finished is logging "NO". – ryan Mar 12 '14 at 18:28

4 Answers4

30

From the UIView documentation:

completion

A block object to be executed when the animation sequence ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle. This parameter may be NULL.

What this means is that there isn't a guarantee that the code will be executed only when the animation is done. I'd advise you to check the "finished" parameter as a condition for execution.

Community
  • 1
  • 1
Stavash
  • 14,244
  • 5
  • 52
  • 80
  • 1
    So, to make sure I understand, are you saying that even if the animation is not interrupted explicitly, and not replaced with another animation, the completion block can still get called before actual completion? Or are those two scenarios the only exceptions you know of? – Nate Mar 19 '13 at 23:10
  • 2
    The only actual experience I've had with this block being called prematurely is when the animation was interrupted before finishing up. – Stavash Mar 20 '13 at 07:16
  • 1
    I have the opposite problem with iOS 9.3 - The completion block is never called ! Any clue why that happens? – RPM Mar 29 '16 at 21:19
  • 1
    Great advise that "check the 'finished' parameter as a condition for execution". Thanks man. – Kemal Can Kaynak Nov 23 '16 at 07:31
  • @RPM did you ever find a solution to this problem. I am having the same issue. – Gustavo_fringe Sep 14 '17 at 15:54
  • 2
    I have the same issue. The 'finished' parameter evaluates to `true` even tho the animation doesn't occur and the completion block is called immediately regardless of how long I have set the duration to. – MikeG Aug 09 '18 at 18:39
  • 1
    This isn't the behavior I'm seeing either. I've got a frame animation that take 2.5 seconds. My completion block is being called immediately and the `finished` parameter is `true`. – Kenny Wyland Sep 08 '18 at 17:18
18

Yes. It is being called too early because it's being interrupted somehow. Probably by a modal presentation transition or perhaps something else. Depending on your needs, the following may be a solution you like. We avoid the conflict by manually delaying the execution of our animation code like so:

// 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
    }];
});
John Erck
  • 9,478
  • 8
  • 61
  • 71
  • 3
    As an alternative you could always invoke [containingView layoutIfNeeded] to clear off the animation stack before calling [UIView animateWith .....] Food for thought. – BonanzaDriver Apr 23 '15 at 23:47
  • Not so beautiful, but this is the only solution that worked to animate after a dismiss transition – Lucas Eduardo Aug 21 '15 at 23:39
  • Thanks, I was searching for a solution for this from yesterday – NDM Feb 23 '17 at 20:26
12

It's also possible for the completion to be called early if the animation has no effect, e.g. setting the alpha of a view to the value it already has.

Luke
  • 7,110
  • 6
  • 45
  • 74
  • 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") }) // it is always going in anim completed – saurabhgoyal795 Apr 06 '18 at 07:16
1

Posting this here in case it helps. In my case I was using a keyframe animation and the completion block was called immediately. Beware these concepts:

  • duration: The duration of the overall animation, measured in seconds. If you specify a negative value or 0, changes are made immediately and without animations.

  • frameStartTime: The time at which to start the specified animations. This value must be in the range 0 to 1, where 0 represents the start of the overall animation and 1 represents the end of the overall animation. For example, for an animation that is two seconds in duration, specifying a start time of 0.5 causes the animations to begin executing one second after the start of the overall animation.

  • frameDuration: The length of time over which to animate to the specified value. This value must be in the range 0 to 1 and indicates the amount of time relative to the overall animation length. If you specify a value of 0, any properties you set in the animations block update immediately at the specified start time. If you specify a nonzero value, the properties animate over that amount of time. For example, for an animation that is two seconds in duration, specifying a duration of 0.5 results in an animation duration of one second.

Setting any of these values wrong will interfere with the time the completion gets called.

jomafer
  • 2,655
  • 1
  • 32
  • 47