Author
Travis Kirton

Estimated Time
10 - 20 minutes


Bees & Bombs

In this tutorial we’re going to reconstruct a lovely gif posted by Bees & Bombs. The specific gif is one they posted on twitter, which is slightly different (only in color) from their dribble post.

Here’s what we’ll be building:

Let’s get to it.

Breakdown

Visually, there are 4 circles, each with a cut-out that rotates (sometimes in sync with the circle, sometimes not). This seems like it would be pretty tough to recreate, what with the changing shape of the cutouts.

However, to recreate this we only need to simulate the cut outs. If we think about the shapes in terms of stacked layers, the animation ends up being pretty straight forward.

How we’ll break things down

To achieve the visual effect of the gif we’re going to use (instead of 4 circles) the following layers:

  1. 1 invisible container to rotate all of the circles
  2. 4 black circles
  3. 4 white wedges that look like cut outs
  4. 1 view with a white background that lookes like all the cutouts put together

10 things not 4!

Here’s a snapshot of the layers as seen from Xcode:

The small white squares are the frames of the wedge cut-outs. The large one in front is the rotating white view.

The Circles & Squares (cutouts)

We can do this step in one shot. What we’ll do is:

  1. Create a container (which handles the rotating back / forth)
  2. Create 2 arrays to reference the circles and the wedges.
  3. Create 4 circles
  4. Add a small wedge to each circle
  5. Style everything

Your setup will look like this:

var circles = [Circle]()
var wedges = [Wedge]()

override func setup() {
    let d = 160.0 //radius of circle
    canvas.backgroundColor = white

    let container = View(frame: Rect(0,0,d,d))
    container.center = canvas.center
    canvas.add(container)
    
    let points = [Point(), Point(d,0), Point(d,d), Point(0,d)]
    for i in 0...3 {
        let circle = Circle(center: points[i], radius: d/2.0 - 5.0)
        circle.fillColor = black
        circle.lineWidth = 0
        circles.append(circle)
        container.add(circle)

        let wedge = Wedge(center: circle.bounds.center, radius: d/2, start: M_PI_2 * Double(i), end: M_PI_2 * (1+Double(i)))
        wedge.fillColor = white
        wedge.lineWidth = 0.0
        wedges.append(wedge)
        circle.add(wedge)
    }
}

Hit Run.

This is what your sketch should now look like! Pretty spot-on, right?

Now, if you comment out the lines that style the wedges, you’ll see that they look like this:

//wedge.fillColor = white
//wedge.lineWidth = 0.0

The Main Square

The animation also has a component where all “the cutouts” move in unison. We’re going to handle this by adding a white view over top of everything so it will look like the wedges are moving together.

Add the following to the end of your setup method

let mainSquare = View(frame: container.frame)
mainSquare.backgroundColor = white
mainSquare.hidden = true
canvas.add(mainSquare)

That’s it. There’s a white square on top of where the wedges already make it look like a white square.

Note: we turned the visibility of the square off for now.

The Animations

When I first tried making the animations it seemed like I could do repeats = true and reverses = true on them. However, I realized that there’s actually a little bit of a gap between rotations, so I found it better to break the rotations into 2 animations that call one another continuously.

Before we continue, add the following constant to your setup method:

let ϴ = M_PI

Forward & Backward

The two animations are broken up into forward (clockwise) and backward (counterclockwise).

The basic steps are:

  1. the circles do a full rotation
  2. the group does a quarter rotation

These steps are applied for both directions.

Forward

The forward animation looks like this:

Add the following to setup:

let containerRotateForward = ViewAnimation(duration: 1.25) {
    for circle in self.circles {
        circle.rotation += ϴ * 2.0 //each circle does a full rotation
    }
    container.rotation += ϴ / 2.0 //the container does a quarter rotation
}
containerRotateForward.delay = 0.25 //the animation waits 0.25s to start
containerRotateForward.curve = .EaseInOut

Backward

The backward rotation looks like this.

Add the following to setup:

let containerRotateBackward = ViewAnimation(duration: 1.25) {
    for circle in self.circles {
        circle.rotation -= ϴ * 2.0 //each circle does a full rotation
    }
    container.rotation -= ϴ / 2.0 //the container does a quarter rotation
    mainSquare.rotation += ϴ / 2.0 //the main square does full rotation
}

containerRotateBackward.delay = 0.25
containerRotateBackward.curve = .EaseInOut

Completion Observers

To create the cycling animation, we’re going to call the forward and backward animations from one another’s completion observers. We’re also going to handle the behaviour of the large square animation by toggling the visibility of the wedges and mainSquare.

Forward Completion

After a forwards rotation we can reveal mainSquare so that it’s ready to spin. We also hide the wedges before initiating containerRotateBackward.

Add the following to setup:

containerRotateForward.addCompletionObserver {
    mainSquare.hidden = false //reveal the main square
    for wedge in self.wedges {
        wedge.hidden = true //hide the wedges
    }
    containerRotateBackward.animate()
}

Backward Completion

The exact opposite of the previous completion, toggles the visibility of the wedges and mainSquare in preparation for the forward animation.

Add the following to setup:

containerRotateBackward.addCompletionObserver {
    mainSquare.hidden = true //hide the main square
    for wedge in self.wedges {
        wedge.hidden = false //reveal the wedges
    }
    containerRotateForward.animate()
}

Animate it!

We’re just about done.

Add the following to the end of setup:

containerRotateForward.animate()

Hit Run!

Finito!

That’s it. We broke down what looked like a complex animation of 4 shapes into a process of animating 6 layers (i.e. 5 shapes, a container and a main square), while toggling 5 separate layers (i.e. the wedges and the main square).

For a complete copy of the code, see this gist: Bees + Bombs

And, if you want to see how this could be done with masks: Bees + Bombs w/ Masks