CA: How to wait for an animation to finish?

  • One thing I still haven't figured out how to do in Core Animation is
    how to schedule an animation to start after previous animations have
    finished. Anyone know how?

    For example, in GeekGameBoard, moving a checkers piece is done by
    (1) Changing the piece's superlayer to be the root layer, instead of
    the square it was in
    (2) Changing its position to the location of the destination square
    (3) Changing its superlayer to the destination square

    The problem is that changing the superlayer in steps 1 and 3 has to be
    done with animations disabled, because it requires simultaneously
    changing the position as well; without disabling animations, the
    superlayer change causes the object to jump in position due to the new
    coord system, after which it animates back to the right location. So I
    wrap this in a CATransaction with animations disabled (see
    ChangeSuperlayer* in the source code.)

    But I found that if step (3) runs before the animation in step (2) has
    completed, it interrupts the animation and causes the piece to jump
    anyway.

    The docs imply that [CATransaction flush] ought to block till the
    previous transaction has completed, but I can't get it to do anything.
    The only workaround I've found has been the very ugly one of calling
    usleep(0.25e6) to wait for step 2 to finish.

    It seems that the expected way to sequence animations is to create a
    CAAnimation; that way I can be called when it completes, or even add
    key-frames. The problem with this is that it requires that all the
    layer property changes be done using a completely different API:
    instead of setting layer's property directly, as I do now, I have to
    create a CAAnimation object, set its parameters, and add it to the
    layer. That means that the lower-level code I have that manipulates
    the layers directly can't be used, and has to be duplicated using this
    different API. There's got to be a better way!

    Thanks...

    —Jens

    * line 66 of http://mooseyard.com/hg/hgwebdir.cgi/GeekGameBoard/file/af9b2b929b03/Source
    /QuartzUtils.m
  • Hi Jens,

    maybe the Delegation Method:

    - (void)animationDidStop:(CAAnimation *)animation finished:
    (BOOL)finished;

    is what you are looking for?!

    regards
    Joachim

    Am 16.03.2008 um 17:03 schrieb Jens Alfke:

    > One thing I still haven't figured out how to do in Core Animation is
    > how to schedule an animation to start after previous animations have
    > finished. Anyone know how?
    >
    > For example, in GeekGameBoard, moving a checkers piece is done by
    > (1) Changing the piece's superlayer to be the root layer, instead of
    > the square it was in
    > (2) Changing its position to the location of the destination square
    > (3) Changing its superlayer to the destination square
    >
    > The problem is that changing the superlayer in steps 1 and 3 has to
    > be done with animations disabled, because it requires simultaneously
    > changing the position as well; without disabling animations, the
    > superlayer change causes the object to jump in position due to the
    > new coord system, after which it animates back to the right
    > location. So I wrap this in a CATransaction with animations disabled
    > (see ChangeSuperlayer* in the source code.)
    >
    > But I found that if step (3) runs before the animation in step (2)
    > has completed, it interrupts the animation and causes the piece to
    > jump anyway.
    >
    > The docs imply that [CATransaction flush] ought to block till the
    > previous transaction has completed, but I can't get it to do
    > anything. The only workaround I've found has been the very ugly one
    > of calling usleep(0.25e6) to wait for step 2 to finish.
    >
    > It seems that the expected way to sequence animations is to create a
    > CAAnimation; that way I can be called when it completes, or even add
    > key-frames. The problem with this is that it requires that all the
    > layer property changes be done using a completely different API:
    > instead of setting layer's property directly, as I do now, I have to
    > create a CAAnimation object, set its parameters, and add it to the
    > layer. That means that the lower-level code I have that manipulates
    > the layers directly can't be used, and has to be duplicated using
    > this different API. There's got to be a better way!
    >
    > Thanks...
    >
    > —Jens
    >
    > * line 66 of http://www.aquarius-software.de
  • On 16 Mar '08, at 9:43 AM, Joachim Deelen wrote:

    > maybe the Delegation Method:
    > - (void)animationDidStop:(CAAnimation *)animation finished:
    > (BOOL)finished;
    > is what you are looking for?!

    It would be, if I had a CAAnimation object. But I don't. All I did was
    something like "layer.position = newPos". That creates an implicit
    animation behind the scenes, but I don't see any way to get the object
    corresponding to that animation, so I have no way of tracking its
    progress.

    (And yes, I could change that line of code to explicitly create a
    CAAnimation instead. But I have tons of code all over my project that
    creates implicit animations, and changing all of it is totally
    impractical.)

    —Jens
  • On 16 Mar '08, at 10:15 AM, I wrote:

    > It would be, if I had a CAAnimation object. But I don't. All I did
    > was something like "layer.position = newPos". That creates an
    > implicit animation behind the scenes, but I don't see any way to get
    > the object corresponding to that animation, so I have no way of
    > tracking its progress.

    After some experimenting I found that setting the "position" property
    of a layer sets an animation for the "position" key; so you can
    retrieve it by calling [layer animationForKey: @"position"]. But
    that's an undocumented implementation detail that could change in the
    future, so I don't want to rely on it. (There is no "animations"
    property that would let me get all the current animations on the
    layer.) Worse, the CAAnimation retrieved from that key has somehow
    been made read-only, so attempting to set its delegate (so I can be
    informed when it finishes) throws an exception.

    The relationship between implicit and explicit animations seems very
    confusing, and I can't find any documentation of it. I'm fighting my
    way through this by trial and error...

    —Jens
  • On Mar 16, 2008, at 9:03 AM, Jens Alfke wrote:

    > One thing I still haven't figured out how to do in Core Animation is
    > how to schedule an animation to start after previous animations have
    > finished. Anyone know how?

    If you're using implicit animations, the best way currently is via
    NSObject's -performSelector:withObject:afterDelay: methods. Or disable
    the implicitly added animations and create the objects by hand, you
    can then either use the delegate callbacks, or just schedule them to
    happen at the right time via the beginTime properties

    > The docs imply that [CATransaction flush] ought to block till the
    > previous transaction has completed, but I can't get it to do
    > anything. The only workaround I've found has been the very ugly one
    > of calling usleep(0.25e6) to wait for step 2 to finish.

    That's not what the comment/documentation is supposed to imply, but I
    can see why you might read it that way, the header file says:

    /* Commits any extant implicit transaction. Will delay the actual commit
      * until any nested explicit transactions have completed. */

    + (void)flush;

    what the second sentence is trying to get across is that if you do
    something like this:

    [CATransaction begin];

    [CATransaction flush];

    [CATransaction commit];

    the actual flush to the render tree is delayed until after the
    outermost commit.

    >
    > It seems that the expected way to sequence animations is to create a
    > CAAnimation; that way I can be called when it completes, or even add
    > key-frames. The problem with this is that it requires that all the
    > layer property changes be done using a completely different API:
    > instead of setting layer's property directly, as I do now, I have to
    > create a CAAnimation object, set its parameters, and add it to the
    > layer. That means that the lower-level code I have that manipulates
    > the layers directly can't be used, and has to be duplicated using
    > this different API. There's got to be a better way!

    Implicit animations are useful in many scenarios, but due to the
    simplicity of the API they don't cover everything. If there are things
    you'd like to see them extended to support please file bugs.

    > [later message]
    >
    > After some experimenting I found that setting the "position"
    > property of a layer sets an animation for the "position" key; so you
    > can retrieve it by calling [layer animationForKey: @"position"]. But
    > that's an undocumented implementation detail that could change in
    > the future,

    this should be documented, you can rely on it (see below)

    > so I don't want to rely on it. (There is no "animations" property
    > that would let me get all the current animations on the layer.)
    > Worse, the CAAnimation retrieved from that key has somehow been made
    > read-only, so attempting to set its delegate (so I can be informed
    > when it finishes) throws an exception.

    Yes, once an animation has been copied and added to the layer it's
    currently made immutable since we have no way to propagate changes
    into the render tree (it's possible this could change in a future
    release)

    >
    > The relationship between implicit and explicit animations seems very
    > confusing, and I can't find any documentation of it. I'm fighting my
    > way through this by trial and error...

    There's no difference between an implicit animation and an explicit
    animation. The only difference is that implicit animations are created
    by the layer's -actionForKey: method in response to a property being
    changed, while explicit animations are created by your code directly.

    This is what happens when a layer property is modified:

    1. Finds the "action" object associated with the property. If the
    disableActions transaction property is true the action is nil,
    otherwise, calls the -actionForKey: method, with the key being the key-
    path of the property being changed. -actionForKey: does these things
    to try to find a non-nil object: (the first that returns non-nil stops
    the others from being tried)

    1. calls the -actionForLayer:forKey: delegate method
    2. looks in the layer's "actions" property (a dictionary)
    3. walks up the "style" hierarchy looking in the "actions" property
    of each style dictionary
    4. calls the +defaultActionForKey: method.
    5. if the key represents an animatable property, creates an animation
    object to represent that animation.

    any of these steps can return [NSNull null] to stop the search.

    2. Calls the -willChangeValueForKey: method

    3. Sets the property to its new value wherever that's stored

    4. Calls the -didChangeValueForKey: method

    5. If the action from step (1) is non-nil, invoke it's -
    runActionForKey:object:arguments: method. In the case of CAAnimation,
    this invokes the layer's -addAnimation:forKey: method to add the
    animation to the layer (the key is the key-path of the changed
    property.)

    Hopefully that makes it clearer, the important thing is that an
    implicit animation is just an explicit animation that was created
    within a property setter via the -actionForKey: mechanism,

    John
  • On 16 Mar '08, at 12:07 PM, John Harper wrote:

    > Implicit animations are useful in many scenarios, but due to the
    > simplicity of the API they don't cover everything. If there are
    > things you'd like to see them extended to support please file bugs.

    Thanks for your detailed answers, John.

    The main problem I run into with implicit animations is that I can't
    make atomic changes to the layer hierarchy and positions.
    Specifically, in GeekGameBoard I often need to change a layer's
    superlayer without altering its onscreen position. (On a mouse-down on
    a game piece, it needs to be removed from the square that contains it
    and added to the root layer so it can be dragged all around the
    board.) The basic calls to do this* are:
    [piece removeFromSuperlayer];
    piece.position = newPositionRelativeToRootLayer;
    [rootLayer addSublayer: piece];
    Unfortunately this has the visual effect of making the piece shoot
    offscreen and then back, because the position change and the
    superlayer change don't happen simultaneously.
    The way around that is to wrap a CATransaction around the whole thing
    and disable animations in it.
    But if there's any animation involving that layer already in progress,
    the supposedly-non-animated layer/position change seem to screw it up,
    and the layer ends up jumping to the wrong place.

    So what I need is a way to say "block until all animations have
    finished" at the start of my ChangeSuperlayer function. Or "queue
    these changes after all current animations finish". That's what I
    hoped +[CATransaction flush] would do, but doesn't.

    —Jens

    * http://mooseyard.com/hg/hgwebdir.cgi/GeekGameBoard/file/af9b2b929b03/Source
    /QuartzUtils.m

      (see ChangeSuperlayer( ) function.)
previous month march 2008 next month
MTWTFSS
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            
Go to today