Author
Oliver Andrews

Estimated Time
10 - 20 minutes


Your mission, should you choose to accept it, is to build the Skype loading animation using C4.

This is what the animation looks like:

This is a great animation to write a tutorial on because although it looks great, it’s fairly simple when it’s broken down. The circles in the animation are identical, except for their start time and duration.

A circle begins animating, then after a short delay another circle begins to animate, until all four circles have started animating. To have all the circles converge near the end of their rotation, their animation duration is less their delay.

Now, I’ll walk you through the steps to recreate this animation.

If you haven’t already done so, install C4 by following the instructions on our install tutorial. The easiest is to use the first option.

Create a new C4 Project

The Views here are great

Though there are 4 circles in the Skype loading animation, we’re going to use an additional 5 invisible views to create the effect we want to achiever.

Let’s get to it.

Create the Views and Circles

In your project’s WorkSpace add two arrays and a View as class variables. Override setup() and set the canvas.backgroundColor to a nice blue and call createViewsCircles().

1
2
3
4
5
6
7
8
9
10
11
class WorkSpace: CanvasController {
    var views = [View]()
    var circles = [Circle]()
    var container: View!

    override func setup() {
        canvas.backgroundColor = Color(red: 64, green: 177, blue: 239, alpha: 1.0)

        createViewsCircles()
    }
}

Create a function called createViewsCircles(). In this function, you’re going to initialize a container, 4 views and 4 circles.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    
func createViewsCircles() {
    container = View(frame: Rect(0,0,1,1))
    for _ in 1...4 {
        let v = View(frame: Rect(0,0,10,10))
        views.append(v)

        let c = Circle(center: v.center, radius: v.width/2.0)
        c.lineWidth = 10.0
        c.strokeColor = white
        c.fillColor = white
        circles.append(c)
        v.add(c)
        v.anchorPoint = Point(0.5,7.5)
        v.center = container.center
        container.add(v)
    }
    canvas.add(container)
    container.center = canvas.center
}

Run It!

This is what you should see:

It looks like a single dot, but dots can be deceiving.

What’s happening???

This method does the following:

  • creates a container
  • loops 4 times, which…
  • creates an invisible 10x10 view
  • into which a 10x10 white circle is added
  • then offsets the anchorPoint of the view
  • which is then added to the container
  • then the container is added to the center of the canvas

Changing the anchor point of a view offsets what it “believes” is its center point.

Why so complex???

There’s a particular motion to the Skype loader. What we found was this:

  • The circles start at the top at full-size
  • The circles start rotating counter-clockwise
  • Each circle’s rotation motion is offset by a fraction of a second
  • When the circles each arrive at approx. 1/8th of a rotation they shrink
  • When the circles each arrive at approx. 7/8th of a rotation they grow
  • This process repeats.

From this process we deduced a couple of things:

  1. The easing and motion of the resizing of the circles is independent of their rotation
  2. The rotation has an .EaseInOut effect throughout its entire motion
  3. The growth pattern takes up about 1/4 of a full rotation

So,…

  1. We use an “non-moving” circle to perform the resize (i.e. not moving within its superview)
  2. We rotate the superview of the circle by 180 degrees easing it in and out
  3. We rotate the container of each view by 180 degress

It is the combination of an .EaseInOut motion of the view’s rotation within the motion of its superview container that creates the proper timing and easing for the entire animation

Create the Animations

There are two major steps in this section: animate the views, and animate the circles. We’ll start with the views.

View Animations

Add the following class variables and a constant for the duration.

1
2
let duration = 3.0
var viewAnimationGroup: ViewAnimationGroup!

Then, add the following function to your WorkSpace:

1
2
3
4
5
6
7
8
9
10
11
12
13
func createAnimations() {
    var vanims = [ViewAnimation]()
    for i in 0..<views.count {
        let v = views[i]
        let offset = Double(i) * 0.1 + 0.05
        let va = ViewAnimation(duration: duration/4.0 + 0.3) {
            v.rotation += M_PI
        }
        va.delay = offset
        vanims.append(va)
    }
    viewAnimationGroup = ViewAnimationGroup(animations: vanims)
}

Each view starts its rotation with a slight offset.

Then, call that method in setup()

1
2
3
4
5
override func setup() {
    canvas.backgroundColor = Color(red: 64, green: 177, blue: 239, alpha: 1.0)
    createViewsCircles()
    createAnimations()
}

Time it Out

For this example, we’re going to trigger the animations with a repeating timer. Add the following variable to your WorkSpace:

1
var animationTimer: Timer!

Then, at the end of setup(), right after creating the animations, add the following:

1
2
3
4
5
animationTimer = Timer(interval: duration/4.0) {
    self.viewAnimationGroup.animate()
}
animationTimer.start()
animationTimer.fire()

Run it!

Press play, and you should see this:

The simple motion of the views.

Shrink and Grow

Now, let’s modify the createAnimations() function to generate animations for the shrinking and growing of each circle. Add another ViewAnimationGroup to the WorkSpace:

1
var circleAnimationGroup: ViewAnimationGroup!

Then, modify createAnimations() to look like:

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
func createAnimations() {
    var canims = [ViewAnimation]()
    var vanims = [ViewAnimation]()
    for i in 0..<views.count {
        let v = views[i]
        let offset = Double(i) * 0.1 + 0.05
        let va = ViewAnimation(duration: duration/4.0 + 0.3) {
            v.rotation += M_PI
        }
        va.delay = offset
        vanims.append(va)

        let c = circles[i]
        let ca = ViewAnimation(duration: duration/8.0 + 0.15) {
            c.lineWidth = 0.0
        }
        ca.delay = offset
        ca.autoreverses = true
        ca.addCompletionObserver {
            ShapeLayer.disableActions = true
            c.lineWidth = 10.0
            ShapeLayer.disableActions = false
        }
        canims.append(ca)
    }
    viewAnimationGroup = ViewAnimationGroup(animations: vanims)
    circleAnimationGroup = ViewAnimationGroup(animations: canims)
}

Next, trigger the circle animations from the timer:

1
2
3
4
animationTimer = Timer(interval: duration/4.0) {
    self.viewAnimationGroup.animate()
    self.circleAnimationGroup.animate()
}

Run it, Again!

Press play, and you should see this:

Nice. Almost there…

Container Animation

The container also needs to rotate, so the last step is to create an animation to handle that. In setup() add the last bit of code.

1
2
3
4
5
6
let containerAnim = ViewAnimation(duration: duration) {
    self.container.rotation += 2*M_PI
}
containerAnim.curve = .Linear
containerAnim.repeats = true
containerAnim.animate()

Call Ended.

Run your app now and you should see something that looks like this:

That was an easy one…

Actually, it took us a long time to decipher the motion of the circles and figure out how to re-construct the animation usin layers and layers of views.

###Code You can grab a copy of the code for this project from HERE