Chaining Core Animation animations

Which is the most elegant and modular way to chain animation in a Core Animation context?

I mean to do animations that starts just when other finished (for example, changing position and then opacity).. normal approach is to directly change properties:

  • Adding relationships in NSManagedObjectModel to programmatically created NSEntityDescription
  • focus a NSTextField
  • Difference between + and - methods in Objective-c
  • Mysterious “progressive slowing” problem in run loop / drawRect
  • Use of an optional value in Swift
  • Using frameworks in a command line tool
  • layer.position = new_point;
    layer.opacity = 0.0f;
    

    but this will do them at the same time. I want to make one wait for the other.

    And what about chaining animations for different objects? I’ve read about CATransaction used like:

    [CATransaction begin]
    layer1.property = new_property;
    [CATransaction begin]
    layer2.property2 = new_property2;
    [CATransaction commit];
    [CATransaction commit];
    

    but it doesn’t seem to work..

    6 Solutions Collect From Internet About “Chaining Core Animation animations”

    You can also use animation grouping and use the beginTime field of the animation. Try something like this:

    CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    [posAnimation setFromValue:[NSNumber numberWithFloat:0.0]];
    [posAnimation setToValue:[NSNumber numberWithFloat:1.0]];
    // Here's the important part
    [posAnimation setDuration:10.0];
    [posAnimation setBeginTime:0.0];
    
    CABasicAnimation *borderWidthAnimation = [CABasicAnimation animationWithKeyPath:@"borderWidth"];
    [borderWidthAnimation setFromValue:[NSNumber numberWithFloat:0.0]];
    [borderWidthAnimation setToValue:[NSNumber numberWithFloat:1.0]];
    // Here's the important part
    [borderWidthAnimation setDuration:10.0];
    [borderWidthAnimation setBeginTime:5.0];
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    [group setDuration:10.0];
    [group setAnimations:[NSArray arrayWithObjects:posAnimation, borderWidthAnimation, nil]];
    
    [layer addAnimation:group forKey:nil];
    

    Notice that the duration of the entire animation is 10 seconds. The first one starts at second 0 and the second one starts at 5 seconds.

    As Matt pointed out, you can create animation groups that consist of different animations for the same layer with different begin times. You can also set a delegate for stand-alone CAAnimation objects or CAAnimation Groups and as each animation finishes it will call an animationDidStop:finished: delegate method (note that animations that are part of a group won’t call their delegate’s animationDidStop:finished: method.

    I figured out a cool trick that makes using the CAAnimation animationDidStop:finished: method more powerful. I use the method setValue:forKey: to add a block of code to a stand-alone animation or animation group, with the key @”animationCompletionBlock”. I then write a general animationDidStop:finished: method that checks the just-completed animation for a @”animationCompletionBlock” key, and if it finds it, execute the block of code there.

    Take a look at this project on github for a working example of that technique:

    CAAnimation demo with completion blocks

    You an also Set a group of animations inside a

    [CATransaction begin];
    //...
    [[CATransaction commit];
    

    block, as you suggested. When you do that, you can using the CATransaction class method setCompletionBlock: to invoke a block of code when all the animations in the current transaction group complete. The completion block for one transaction can then trigger the next transaction.

    I don’t believe you can “nest” CA animations as you have in your example.

    You need to specify a delegate for the animation and put your second “transition” within the animationDidStop:finished: selector of the delegate.

    Might want to have a look at Apple’s Animation Types & Timing Programming Guide.

    What I have always preferred to setting the begin and end time of each animation seperately is this:

    I used A2DynamicDelegate (whose development is now happening in the BlocksKit-Repo, who knows why <_<) to implement a completionBlock property in a Category on CAAnimation.

    This enabled me to do stuff like this:

    CAAnimation *a = ...
    CAAnimation *b = ...
    CAAnimation *c = ...
    
    a.completionHandler = ^{
        [self.layer addAnimation:b forKey:@"foo"];
        [self.layer addAnimation:c forKey:@"bar"];
    };
    

    Much more flexible đŸ™‚

    I’ve uploaded my code for completion handler here. Have a look at the notice in the header file though. I am really confused why the method isn’t called.

    I pull this off using the setCompletionBlock method to define a closure that triggers the next animation when the first one is finished:

    [CATransaction begin]
    layer1.property = new_property;
    CATransaction.setCompletionBlock {
        [CATransaction begin]
        layer2.property2 = new_property2;
        [CATransaction commit];
    }
    [CATransaction commit];
    

    Without including all the “tricks” up my “toolchain”, this example isn’t directly copy/pastable… but it does show a REALLY easy strategy for “chained” animations..

    CATransform3D trans = m34();  // define chain-wide constants.
    // Name your "stack". My "nextObject" returns arr[i]->done == nil. 
    NSArray *layerStack = layer.sublayers;
    //define a block, that "takes" a layer as it's argument.
    void(^__block ChainBlock)(CALayer*) = ^(CALayer *m) { 
    // animations, transforms, etc for each inividual "step".
        [m animate:@"transform" 
              // These are just NSValue-wrapped CAT3D's
              from:AZV3d(CATransform3DRotate(trans,  0,1,0,0)) 
                to:AZV3d(CATransform3DRotate(trans,1.5,1,0,0)) 
              time:2    // total time == each step * layerStack.count
             eased:kCAMediaTimingFunctionEaseOut
        completion:^{   // In completion, look for "next" layer.
                        CAL*  m2 = [layers nextObject]; 
    // If there is "another" layer, call this block, again... with it.
                          if (m2)  chainAnis(m2);
    // Otherise,you're done.  Cleanup, toggle values, whatevs.
                         else self.finishedProperty = YES;
        }];
    };
    // Give the block we just defined your "first" layer.
    ChainBlock(layerStack[0]);  // It will recursively feed itself.
    

    This obviously depends on some “external magic”, but the concept is simple, and eliminates (through dependencies) the need to “deal with” ANY sort of gross delegation. In particular, the animate:from:to:time:easing:completion, etc. categories come from the great FunSize Framework, on Github.