My book on iOS interface design, Design Teardowns: Step-by-step iOS interface design walkthroughs is now available!

Creating animation sequences

Thanks to @borndangerous and @bradjasper for their suggestions.


Creating natural looking animations has always been hard. But with Pop and its extension MCAnimate, it doesn't have to be. (Disclaimer: I maintain the MCAnimate category.)

Today, we'll go ahead to recreate an animation by Andy Barnard from his post Animation Timing on iOS (if you're into serious animation on iOS, it's definitely worth a read.)

Preview of animation

Firstly, we'll create views for each of the red circles and then store them away in an array:

CGPoint center = CGPointMake(CGRectGetWidth(self.view.bounds) / 2, CGRectGetHeight(self.view.bounds) / 2);

NSMutableArray *circles = [NSMutableArray array];

for (int i = 0; i < kNumberOfCircles; i++) {  
    UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kCircleSize, kCircleSize)];
    circle.backgroundColor = [UIColor colorWithRed:0.945 green:0.439 blue:0.416 alpha:1];
    circle.layer.cornerRadius = kCircleSize / 2;
    circle.center = center;
    circle.alpha = 0;
    circle.scaleXY = CGPointMake(0.5, 0.5);

    [self.view addSubview:circle];
    [circles addObject:circle];
}

self.circles = [circles copy];  

We're simply using the cornerRadius property on CALayer to give the impression of a circle. We then start the circles off with a smaller scale and make them fully transparent.

In the first stage of the animation, we move the circles out to a certain radius, scale them up to full size, and make them visible. But each circle is slightly delayed to create a staggered animation. This is achieved using the beginTime property (specifying the delay in seconds) in Core Animation and Pop. In MCAnimate, this is really trivial:

[circles sequenceWithInterval:0.1 animations:^(UIView *circle, NSInteger index){
    CGPoint position = center;
    position.x += kCircleRadius * sin(angleIncrement * index);
    position.y -= kCircleRadius * cos(angleIncrement * index);

    circle.spring.center = position;
    circle.spring.alpha = 1;
    circle.spring.scaleXY = CGPointMake(1, 1);
} completion:^(BOOL finished){
}];

Having specified the interval between animations, the library knows how to iterate through the array applying animations to each element. For convenience, the element and its index in the array is provided as parameters to the animation block.

What's left is essentially a trigonometric problem - using sin() and cos() to determine the position around a circle's center.

In the next stage, we move the circle elements out further still (at twice the initial radius):

[circles sequenceWithInterval:0 animations:^(UIView *circle, NSInteger index){
    CGPoint position = center;
    position.x += 2 * kCircleRadius * sin(angleIncrement * index);
    position.y -= 2 * kCircleRadius * cos(angleIncrement * index);

    circle.spring.center = position;
} completion:^(BOOL finished){            
}];            

This time we are using an interval of 0 to have the elements animate all at the same time.

Next, we animate the elements a step anti-clockwise and then back:

[circles sequenceWithInterval:0 animations:^(UIView *circle, NSInteger index){
    CGPoint position = center;
    position.x += 2 * kCircleRadius * sin(angleIncrement * (index-1));
    position.y -= 2 * kCircleRadius * cos(angleIncrement * (index-1));

    circle.spring.center = position;
} completion:^(BOOL finished){
    [circles sequenceWithInterval:0 animations:^(UIView *circle, NSInteger index){
        CGPoint position = center;
        position.x += 2 * kCircleRadius * sin(angleIncrement * index);
        position.y -= 2 * kCircleRadius * cos(angleIncrement * index);

        circle.spring.center = position;
    } completion:^(BOOL finished){
    }];
}];

Here's where the index parameter comes in handy. We simply subtract 1 to calculate the position of the previous element (which gives the effect of moving anti-clockwise).

And finally, we reverse the initial animation, moving all elements back to the center, scaling them down and making them transparent:

[circles sequenceWithInterval:0.1 animations:^(UIView *circle, NSInteger index){
    circle.spring.center = center;
    circle.spring.alpha = 0;
    circle.spring.scaleXY = CGPointMake(0.5, 0.5);
} completion:nil];

I hope you enjoyed the short breakdown. Check out the demo project to see this in action.