iOS, Tutorials
comments 2

Creative-coding on iOS with C4 – Tutorial

MO5

This tutorial will introduce you to creative-coding on iOS with C4, a powerful framework for creating expressive artworks and user experiences. Written entirely in Swift, C4 takes a modern approach to working with animation, gestures and media. For example, there are no draw loops – you create animations by defining states and changing properties. You can attach a gesture to any object by using a simple block of code, and easily customize the number of touches, taps and more, to trigger it. You can create images, audio and video players by simply stating the name of a file, and with images you can apply filters. Yet, to say that C4 handles only animation, interaction and media would do it a disservice. There is vector math, an event system for sending messages between objects, a transform class for performing matrix calculations, camera access, and much more.

What You’ll Learn

To give you a good sense of what you can achieve with C4, this tutorial will cover the following key concepts: Properties, Layers, Animations and Gestures. The examples have been carefully designed to introduce you to a wide variety of the objects, tricks and subtle techniques that make C4 shine.

Here’s a short video of the 5 demos that you’ll build in this tutorial:

Made with C4


C4 is broad framework that is integrated tightly with Apple’s native UIKit. You can create both experimental creative works or integrate it directly into ready-to-ship professional quality apps. The following images and videos will give you a good overview of the different kinds of things that have been made with C4.

M/O

Currently being built with C4, M/O is an interactive artwork made up of 28 iPads.

COSMOS

Released in late 2015, the COSMOS app is designed to show how beautiful user experiences can be created with C4. We also published an end-to-end tutorial on how we designed, built and launched to the App Store.

SAP Concept Apps

SAP has used C4 to produce a variety of concept apps for their keynote presentations at conferences such as Sapphire.

Halo

In 2013, a basic form of C4 was used to generate the animations for an interactive light wall. Halo was a collaboration between myself and Tangible Interaction.

Getting Started


Here’s what you’ll need to go through this tutorial:

  1. A Mac
  2. Xcode 7+
  3. C4

Since C4 is built on Apple’s iOS frameworks, it is currently only availble for Mac. You can download the latest copy of Xcode for free right from Apple’s Website.

Installing C4

The easiest way to create new projects is to install C4 directly into Xcode. I highly recommend this approach because it makes it extremely easy to quickly create new projects and test out your ideas.

Here’s the process:

  1. Download the installer
  2. Double-click the .pkg to open it up
  3. Follow the instructions to install
  4. Open Xcode

Running Your First Project

At this point, you’ll see a dialog that looks like this:

xcodeprompt

  1. Select “Create New Xcode Project”
  2. Give your project a name
  3. Choose the device to build for
  4. Save the project somewhere
  5. When your project opens, choose the WorkSpace.swift file
  6. Hit Run

That’s it! You’ve created a new C4 project.

Yes… You just created an iOS app.

Like I mentioned above, I personally think this is the easiest way to test C4 projects. You can quickly create new projects, all the test media are included, and its an easy setup for copying and pasting any example code from our site straight into the WorkSpace. Furthermore, the code that installs is static so it should always work out of the box.

The only significant drawback is that this installs a static copy of C4’s code base in every project you create. If there are updates to the core C4 project on Github this installer won’t update automagically. You’ll have to wait until we create a new installer build.

There are a few other ways to get your hands on C4. You can use Cocoapods, Carthage, Github for Mac, or work purely from git. If you want to explore these options have a look at the install page.

The Basics

Here is a basic example that shows how to put a circle on the screen:

override func setup() {
    let circle = Circle(center: canvas.center, radius: 50)
    canvas.add(circle)
}

circleI recommend you ave a look at our guide to some of C4’s basic features. This is a set of about two dozen very short code snippets that will introduce you to a variety of objects and techniques you can apply in C4.

C4 v Other Frameworks


Inspired by Processing and OpenFrameworks, one of the main goals of C4 is to help people learn how to code and to open the door to native programming. The API is so designed that while you’re learning it you’re actually learning how to program in Swift. There are many similarities with P5/OF, but there are some fundamental differences as well.

We Don’t draw() Here

The most fundamental difference between C4 and other projects like Processing, OpenFrameworks, and Cinder, is working with animations rather than drawing graphics. Instead of writing code to update the animation of a sketch at 30fps (or higher) the focus is on creating discrete animations.

From Black to White

Here’s how you might fade a background color from black to white and back again in Processing:

int val = 255;
int dir = -1;

void setup() {
  size(640, 360);
  background(0);
}

void draw() {
  background(val,val,val);
  val += dir;
  if (val == 255 || val == 0) {
    dir *= -1;
  }
}

To do the same thing in C4, you can add an animation:

override func setup() {
    let a = ViewAnimation(duration: 4.0) {
        self.canvas.backgroundColor = black
    }
    a.repeats = true
    a.autoreverses = true
    a.animate()
}

The animation approach means that you don’t have to figure out every step along an animation. You simply set the beginning / end states and let C4 do the rest!

Everything is an Object

C4 uses objects instead of drawings to represent visuals elements. Compared to other frameworks, the process of adding and positioning basic elements is similar.

An Ellipse

P02Here’s how you draw a simple ellipse in Processing:

void draw() {
  ellipse(width/2,height/2,50,50);
}

In C4, the process is similar:

func setup() {
    let c = Circle(center: canvas.center, radius: 50)
    canvas.add(c)
}

The major difference in the two approaches – objects v. renderings – is what you can do with the object after it’s on the canvas. You can add gesture recognizers or animations to it, and change its visual style all without redrawing. You can even add an object to another object!

Layers

Layers are an important concept in C4. You can achieve complex aesthetics with a few lines of code.

Here’s an example of adding one object to another and animating them both:

The clear circle is actually turning!

Here’s the code for this example:

override func setup() {
    let c = Circle(center: canvas.center, radius: 50)
    c.fillColor = clear

    let sc = Circle(center: Point(), radius: 10)
    c.add(sc)

    let a = ViewAnimation(duration: 2.0) {
        sc.center = c.bounds.max
        c.rotation = 2*M_PI
    }
    a.autoreverses = true
    a.repeats = true
    a.animate()

    canvas.add(c)
}

The small circle sc inherits the animation of its parent layer.

Processing Examples

Processing was a major influence on the development of C4. If you’ve used it in the past, and would like to know how C4 differs, we’ve recreated a handful of Processing examples in C4.

P00

Code

You can grab a copy of the code for this project from here

Waves


In this section we’re going to introduce you to an important concept in C4: Animations. We’ll also show you how to work with gradients and timers to create a wave effect. When we’re done, we’ll show you how to tweak a single line of code to get some different aesthetics out of the example.

Here are some examples of the animation you’ll be building:

Create a new Project

Start by creating a new C4 project called Waves.

Animations

In C4, you’ll be working a lot with objects that contain properties. For example, all shapes have a fillColor and strokeColor. C4 lets you create animations by changing the properties of objects. Rather than calculating all the steps between a and b, you can focus on thinking about states. For example, the following snippet will create a 0.25 second animation where a circle’s fill color will shift to red:

ViewAnimation(duration: 0.25) {
    circle.fillColor = red
}

Now’s a good time to check out the basics of animations and properties over at: C4 Basics

Gradients

For this section, we’re going to create a wave of gradients by using a for loop and a timer to offset our animations. You can create a simple gradient by giving it a frame and a set of colors, like so:

override func setup() {
    let g = Gradient(frame: Rect(0,0,40,200))
    g.colors = [C4Pink,C4Blue]
    g.center = canvas.center
    canvas.add(g)
}

W05By default, the gradient draws from top to bottom of its frame. If you want to change that you can specify a new value for either its startPoint or endPoint.

By adding the following line to setup():

g.endPoint = Point(1,1)

… the gradient will draw from its top-left corner to its bottom-right:

W06

Note: when setting the end points you do so with relative values. The corners of the gradient’s frame are {0,0}{1,0}{1,1} and {0,1} read clockwise from the top-right.

Timers

By default, there are no draw-loops in C4, which means that we can’t trigger animations based on frame counts. But, there are a variety of techniques that we can use to offset the triggering of code. For example, we could use a wait that will execute a bit of code after a specified amount of time (measured in seconds).

wait(0.25) {
    //run a function
}

Waits are great for simple circumstances. You can tell the application to wait a quarter second before doing something. However, they become unreliable when you start chaining them together.

If you want a lot of animations to start at regular intervals, the easiest technique is to use a Timer. You create one by specifying an interval and a block of code to execute. For example, printing to the console once every quarter-second looks like this:

var timer: Timer!

override func setup() {
    timer = Timer(interval: 0.25) {
        print("Hello C4")
    }
    timer.start()
}

NOTE: The timer has to be set as a variable outside the function in which you create it.

And, if you want to know how many times the timer has fired, you can do this:

var timer: Timer!

override func setup() {
    timer = Timer(interval: 0.25) {
        print("Hello C4: \(self.timer.step)")
    }
    timer.start()
}

This will continuously print like so: Hello C4: 1,Hello C4: 2,Hello C4: 3,…

And, if you want to have the timer stop after a given number of iterations, you can do this:

var timer: Timer!

override func setup() {
    timer = Timer(interval: 0.25, count: 10) {
        print("Hello : \(self.timer.step)")
    }
    timer.start()
}

This stops after the timer has fired 10x.

As you can see, timers are easy to work with and give you a range of flexibility in starting, stopping, and changing their parameters.

Now that we’ve had an overview of the 3 main components of this section, let’s build it!

The Wave

To create our wave we’re going to follow this 4-step process:

  1. Build a function that creates a gradient and adds it to the canvas
  2. Build a function to customize an animation
  3. Use a repeat loop to create a lot of gradients
  4. Use a timer to initiates the animation for each gradient

The Gradient Function

The main reason we want to have a function that creates our gradient is because we’re going to call it over and over again. This step is easy, and nearly the same as above (except for some minor changes).

Here is what your main WorkSpace should look like:

class WorkSpace: CanvasController {
    override func setup() {
        createGradient(canvas.center)
    }

    func createGradient(point: Point) -> Gradient {
        var colors = [C4Blue,C4Pink]

        if random(below: 2) == 1 {
            colors = [C4Pink,C4Blue]
        }

        let g = Gradient(frame: Rect(0,0,40,200))
        g.colors = colors
        g.center = point
        canvas.add(g)
        return g
    }
}

This produces the same image as our first experiment with gradients.

The following line creates a random variable, either 0 or 1:

random(below: 2) == 1

We use the result to determine whether we invert the colors of the gradient.

The name of the function is:

createGradient(point: Point) -> Gradient

This specifies that the function requires you to give it a point, and when it has completed its job it will return to you a new Gradient object. Which is why we see the return g statement at the end of the function.

Go ahead and run the code to see what this produces.

Before you move on to the next step, make sure to change from this:

Rect(0, 0, 40, 200)

…to:

Rect(0, 0, 2, 2)

The Animation Function

The effect we’re going for is to have the gradient stretch upwards and downwards at the same time, while keeping its center position. To do so, add the following function to your WorkSpace:

func createAnimation(g: Gradient) {
    let anim = ViewAnimation(duration: 2.0) {
        var f = g.frame
        let c = g.center
        f.height = 100
        f.center = c
        g.frame = f
    }
    anim.curve = .EaseInOut
    anim.autoreverses = true
    anim.repeats = true
    anim.animate()
}

This function requires you to pass it a gradient. It then creates a new animation that does the following:

  1. Creates a new frame
  2. Copies the original frame’s center
  3. Stretches the new frame’s height
  4. Re-centers the new frame
  5. Sets the gradient’s frame to the new frame

The animation takes place over 2 seconds and when it completes it will reverse, and then repeat. This makes the gradient look like it grows and shrinks forever.

Now, change your setup() to look like this:

override func setup() {
    let g = createGradient(canvas.center)
    createAnimation(g)
}

After running your project, you should see something like this:

W07

The Repeat

Next, we want to create a LOT of gradients. Since we have our function ready to do, all we need to do is change setup to run a loop so that we build enough objects to run across the screen.

Do this:

var gradients = [Gradient]()
override func setup() {
    var x = 2.0
    repeat {
        gradients.append(createGradient(Point(x,canvas.center.y)))
        x += 3.0
    } while x < canvas.width
}

We want to keep track of our gradients, so we create an array that will hold them for us.

Run it, and you’ll see this:

The Timer

Let’s bring those gradients to life with a timer.

First, create a new timer variable outside of setup:

var timer: Timer!

Then, back inside setup and after the repeat loop, add the following:

timer = Timer(interval: 0.02, count: gradients.count) { () -> () in
    let g = self.gradients[self.timer.step]
    self.createAnimation(g)
}
timer.start()

This bit of code creates a new timer that fires 50x per second (i.e. 0.02, and once for every gradient in our array (i.e. gradients.count).

Since the timer starts its step at 0, the following lines of code grab the next gradient in the array and animate it:

let g = self.gradients[self.timer.step]
self.createAnim(g)

After creating the timer, a call to timer.start() kicks things off.

Run your code and you’ll see this:

EXPERIMENT!

Now that you’ve created your waves, try playing around with some variables.

In the createGradient function, you can add a transformation to the gradient like so:

g.transform.rotate(M_PI_4)

Try changing the variable to 0.050.2, and M_PI_4 to see some unique renderings.

Also, try playing with any of the other variables in the code!

Code

You can grab a copy of the code for this section from here

Layers


In C4 all visible objects are layers. You can add them on top of one another, send them to the front or back, or change their z-position. More importantly, you can add a layer to another layer. A powerful component of C4, you can take advantage of the layering mechanism to create brilliant and complex effects.

In this section you’ll build a simple loop to create a layered effect. You’ll then be able to explore layers by making slight modifications to that loop.

Create a new Project

Start by creating a new C4 project called Layers.

The Loop

Start by adding 20 circles to the canvas, each with a unique size, alternating colors, and set its anchorPoint to its bottom-center.

override func setup() {
    for i in 1...20 {
        let s = Circle(center: canvas.center, radius: Double(50 - i * 2))
        s.anchorPoint = Point(0.5, 1.0)
        s.center = canvas.center
        s.lineWidth = 0

        if i % 2 == 0 {
            s.fillColor = C4Pink
        }

        canvas.add(s)
    }
}

Here’s the effect of that loop:

W08

Notice how the bottom of each shape is right on the center of the screen? This is because we changed the anchorPoint of each shape before centering it. When you change the center property of a visible object, the object will orient itself based on its anchorPoint.

The anchorPoint is measured in normalized coordinates, where {0,0} is the top-left, {0.5,0.5} is the center, and {1.0,1.0} is the bottom-right.

Simple Animation

Currently, the shapes are layered directly on top of one another. To see them in action, create the following function:

func basicRotate(shape: Shape) {
    let a = ViewAnimation(duration: 5.0) {
        shape.rotation += M_PI
    }
    a.addCompletionObserver {
        a.animate()
    }
    a.animate()
}

This will make a specific shape perform a 180 degree rotation over the course of 1 second.

In C4, angles are measured in radians.

Now, add a call to this animation after adding the shape to the canvas, like so:

override func setup() {
    for i in 1...20 {
        //... setup code
        canvas.add(s)
        basicRotate(s)
    }
}

Now, run the sketch and see how this looks:

Delayed Rotation

Now, let’s offset the starting of each object’s animation by a fraction of a second.

func delayRotate(shape: Shape, delayTime: Double) {
    let a = ViewAnimation(duration: 5.0) {
        shape.rotation += M_PI
    }
    a.addCompletionObserver {
        a.delay = 0.0
        a.animate()
    }
    a.delay = delayTime
    a.animate()
}

This function adds a delay to the animation, then when the animation completes it sets that value back to 0.0. Setting the delay back to zero means the delay happens only on the first iteration of the animation.

Delayed Rotation Variables

Changing the delayTime to…

delayRotate(s, delayTime: 0.25 * Double(i))

… causes a dramatic difference:

Or, simply removing the a.delay = 0.0 will cause the animation to look generative or chaotic.

Try playing with some variables for delayTime

Embedded Layers

The previous examples added each shape to the canvas. Now, we’re going to add each successive shape to the previous one.

Change setup() to look like the following:

var previous: Shape!
override func setup() {
    for i in 1...20 {
        let s = Circle(center: canvas.center, radius: Double(50 - i * 2))
        s.anchorPoint = Point(0.5, 1.0)
        s.lineWidth = 0

        if i % 2 == 0 {
            s.fillColor = C4Pink
        }

        if i == 1 {
            s.center = canvas.center
            canvas.add(s)
        } else {
            s.center = previous.bounds.center
            previous.add(s)
        }

        previous = s
    }
}

Outside the setup you create a variable to keep a reference to the previous shape. Then, you loop through all 20 shapes in much the same way as the prior setup.

This does two different things: 1) it adds the first object, i.e. if i == 1, to the canvas otherwise it adds the new object to the previous one. 2) After setting up an object, it sets it to the previous variable, i.e. previous = s so the next object can reference it.

This is what it looks like:

L04

Notice, the first shapes anchor to the center of the previous shape. You can’t see all the shapes because they are positioned off-screen.

Basic Layered Rotate

This step is easy. Add a call to basicRotate at the end of setup():

override func setup() {
    for i in 1...20 {
        //... setup code
        previous = s
        basicRotate(s)
    }
}

Here’s what you should see:

Too Quick

Pretty fast right??? Try changing the duration of the animation to 5.0, do this for both basicRotate and delayRotate.

Delay Rotate

Add this call to setup in place of basicRotate:

delayRotate(s, delayTime: 0.25 * Double(i))

Try playing with some variables for delayTime, and removing the a.delay = 0.0

Randomize!

The last step we’re going to take is to use a timer to trigger our animations.

Here’s what your project should look like:

class WorkSpace: CanvasController {
    var timer: Timer!
    var previous: Shape!
    var shapes = [Shape]()

    override func setup() {
        for i in 1...20 {
            let s = Circle(center: canvas.center, radius: Double(50 - i * 2))
            s.anchorPoint = Point(0.5, 1.0)
            s.lineWidth = 0

            if i == 1 {
                s.center = canvas.center
                canvas.add(s)
            } else {
                s.center = previous.bounds.center
                previous.add(s)
            }
            previous = s

            if i % 2 == 0 {
                s.fillColor = C4Pink
            }
            shapes.append(s)
        }

        timer = Timer(interval: 1.0) {
            self.animate()
        }
        timer.fire()
        timer.start()
    }

    func animate() {
        for s in shapes {
            ViewAnimation(duration: random01() * 0.75 + 0.25) {
                let dir = random(below: 2) == 1 ? 1.0 : -1.0
                let angle = random01() * M_PI_4
                s.rotation += angle * dir
                }.animate()
        }
    }
}

This setup is pretty close to our first examples, except that it doesn’t apply an animation to each shape. Instead, it creates an array of shapes:

var shapes = [Shape]()

override func setup() {
    for i in 1...20 {
        //... setup code
        shapes.append(s)
    }
}

Then, it creates a timer that fires an animation function:

timer = Timer(interval: 1.0) {
    self.animate()
}
timer.fire()
timer.start()

And, this is what the animation function looks like:

func animate() {
    for s in shapes {
        ViewAnimation(duration: random01() * 0.75 + 0.25) {
            let dir = random(below: 2) == 1 ? 1.0 : -1.0
            let angle = random01() * M_PI_4
            s.rotation += angle * dir
        }.animate()
    }
}

For every shape in the array, it creates an animation with a random duration, i.e. random01() * 0.75 + 0.25, with a random angle and direction.

FIN

As you can see, using layers is pretty rad. You can generate some interesting effects by playing with simple animation delays and settings.

Code

You can grab a copy of the final code for this section here

Microbes


In this section we are going to build a little petri dish full of randomly moving microbes. To achieve a generative effect we will use randomization throughout our example. We’ll also use a pan gesture to create microbes. Invisible animated layers will make the motion of our microbes look more natural and complex.

Create a new Project

Start by creating a new C4 project called Microbes.

M01

A Single Microbe

The concept for this example is this: as you drag your finger across the screen you give birth to little microbes. They then scurry to the center of the screen, after which they disperse throughout an invisible petri dish. Since all the microbes will have the same behaviour, all we need to do is create one and fine-tune it’s behaviour.

class ViewController: CanvasController {
    override func setup() {
        let microbe = createMicrobe(canvas.center)
        canvas.add(microbe)
        randomMove(microbe)
    }

    func createMicrobe(center: Point) -> Circle {
        let microbe = Circle(center: center, radius: 2)
        microbe.lineWidth = 0
        microbe.fillColor = C4Pink

        let a = ViewAnimation(duration: 0.5) {
            microbe.fillColor = C4Blue
        }
        a.autoreverses = true
        a.repeats = true
        a.animate()

        return microbe
    }

    func randomMove(microbe: Circle) {
        let randomMove = ViewAnimation(duration: 0.5) {
            microbe.center = Point(random01()*self.canvas.width,random01()*self.canvas.height)
        }
        randomMove.addCompletionObserver { () -> Void in
            randomMove.animate()
        }
        randomMove.animate()
    }
}

This snippet does three things:

  1. It creates a little microbe (which is just a pink circle), whose colors shift from C4Pink to C4Blue
  2. It creates an animation that moves the microbe randomly across the screen.
  3. The animation repeats by adding itself to its own completion observer

This randomized animation is essentially the final product of our example, but it’s a bit raw. By adding characteristics and constraints, we’re going to make the microbe’s motion look good.

Modified Movement

Since we’re going to simulate microbes in a petri dish, we want their motion constrained to a circle. Let’s modify the randomMove() function to do just that:

func randomMove(microbe: Circle) {
    let anim = ViewAnimation(duration: 0.5) { () -> Void in
        let θ = random01() * 2 * M_PI
        let r = 150 * random01()
        let c = Point(r * sin(θ), r * cos(θ)) + Vector(self.canvas.center)
        microbe.center = c
    }
    anim.addCompletionObserver { () -> Void in
        self.randomMove(microbe)
    }
    anim.animate()
}

This function now uses polar coordinate math to calculate the random position of the microbe. Now, the microbe will randomly move within 150 points of the center of the canvas.

diagram

Run the app now and you’ll see that the microbe never leaves the area around the center of the canvas.

Still Erratic

The microbe are still erratic and they should rest between moving. If there were a lot of microbes on the canvas they would look like this:

In randomMove() change:

duration: 0.5

… to:

duration: random01() * 5 + 2.0

This will set the time of the microbe’s movement to a random value between 2.0 and 7.0 seconds.

Then, set the animation’s delay property like so:

anim.delay = random01()

This will make sure there’s a random gap from 0.0 to 1.0 seconds between each movement.

When you run the example now, you’ll see that the microbe moves much slower and rests between its movements. This slow pace is okay because the end result of our sketch will have a lot of microbes moving at the same time.

Your random move function should now look like this:

func randomMove(microbe: Circle) {
    let anim = ViewAnimation(duration: random01() * 5 + 2.0) { () -> Void in
        let θ = random01() * 2 * M_PI
        let r = 160 * random01()
        let c = Point(r * sin(θ), r * cos(θ)) + Vector(self.canvas.center)
        microbe.center = c
    }
    anim.delay = random01()
    anim.addCompletionObserver { () -> Void in
        self.randomMove(microbe)
    }
    anim.animate()
}

The Pan Gesture

As you drag your finger across the screen microbes should appear. Let’s add a pan gesture and use its location to add microbes.

Change your setup() function to look like this:

override func setup() {
    canvas.addPanGestureRecognizer { (locations, center, translation, velocity, state) -> () in
        ShapeLayer.disableActions = true
        let microbe = self.createMicrobe(center)
        self.canvas.add(microbe)
        self.randomMove(microbe)
        ShapeLayer.disableActions = false
    }
}

The pan gesture creates a microbe at its current location, adds it to the canvas and then starts its movement. The calls to disableActions are helpers to make sure that the color and line width styles don’t animate when we create the shapes.

Try changing the disableActions = true to false to see the effect

As you drag your finger across the screen the number of microbes continues to get bigger. Let’s now add a function that will randomly remove them from the screen after a time.

func fade(microbe: Circle) {
    let a = ViewAnimation(duration: 0.25) {
        microbe.opacity = 0.0
    }
    a.addCompletionObserver { () -> Void in
        microbe.removeFromSuperview()
    }
    a.delay = random01() * 5 + 5
    a.animate()
}

Then, add the following line to your setup() function:

self.fade(microbe)

The fade function adds an animation that has a long delay, between 5.0 and 10.0 seconds. When it triggers, the microbe will fade out and then remove itself from the canvas.

If you run the example now you should see the microbes fading out as you constantly move your finger.

Move to the Center

It’s a nice effect to have the microbes move to the center of the canvas before randomizing. This step requires two changes. First, add the following function:

func moveToCenter(microbe: Circle) {
    let a = ViewAnimation(duration: random01() * 0.5 + 0.5) { () -> Void in
        microbe.center = self.canvas.center
    }
    a.addCompletionObserver { () -> Void in
        self.randomMove(microbe)
        self.fade(microbe)
    }
    a.animate()
}

Then, in setup() replace the following lines:

self.randomMove(microbe)
self.fade(microbe)

… with:

self.moveToCenter(microbe)

The Petris

This step will apply a nice effect to the motion of the microbes. You will create 2 invisible petri dishes, rotate them in random directions. After adding each of the microbes to one of the dishes the rotation of that dish will get applied to the motion of the microbe.

This step requires a lot of little changes. Instead of walking through them step-by-step, we’ll just look at the concept of each step. At the end, you’ll be able to grab a copy of the final code for this section.

Creating the Petris

First, we create a new variable for keeping track of the petris:

var petris = [C4Circle]()

This will allow us to reference them after we create and add them to the canvas.

Next, we create a function that sets them up:

func createPetris() {
        for _ in 0...5 {
            //create the petri
            //style the petri
            //rotate the petri
        }
    }
}

This creates 5 petris and gets them moving.

The more petris we have the more random the motion will be in the final example.

Rotating the Petris

Then, we create a random rotate function, which is essentially the same as randomMove except it applies a rotation instead of a new center.

func randomRotate(petri: Circle) {
    //...
}

Keeping Track of the Petris

Finally, we modify moveToCenter: and randomMove: to take a second parameter. Each shape will need to know which petri it should be added to, and then move itself within the frame of that petri.

func moveToCenter(microbe: Circle, ofPetri petri: Circle) {
    //...
}
func randomMove(microbe: Circle, inPetri petri: Circle) { 
    //...
}

Fin

Our final result looks nice. We have applied randomness to movements and rotations, and have dynamically layered the microbes. On top of that, we’ve created microbes from a pan gesture and gave them a nice centering animation. Finally, we remove old microbes from the canvas to make way for new generations of interaction.

Code

You can grab a copy of the final code for this section from here.

Spark


C4 is great at creating animations between states, and since it uses Core Animation it is efficient at drawing. But, it can also handle running dynamic animations at high frame rates. In this tutorial you’ll learn how to create a displayLink to achieve near 60fps animations. You’ll also learn how to use masks and modify the points of a shape.

Create a new Project

Start by creating a new C4 project called Spark.

Going through this section step-by-step is possible, but not the greatest use of time. Instead, we will discuss the major points of the sketch. This will focus us on experimenting with initial variables to achieve different aesthetic effects.

Grab a copy of the code for this project from here, and follow along.

Three Shapes

The shape we’re going to be playing with consists of 3 shapes:

  • An outer polygon
  • An inner polygon
  • A mask polygon

The effect will be this: the outer shape will appear masked by the inner shape. Here’s a breakdown of the layers:

The outer layer

outer

The inner layer

inner

Overlaid layers

overlaid

The inner layer masked

masked

To run a frame-based animation you need to create a Display Link. This link will run a function whenever the device redraws its screen. The device will always try to run as fast as possible, which means that your animation will run as fast as the device, up to 60fps.

Here’s how you create a display link:

func initializeDisplayLink() {
    let displayLink = CADisplayLink(target: self, selector: "update")
    displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
}

Updating

The algorithm for updating points is straightforward. First, here is the function that gets called by the display link:

func update() {
    ViewAnimation(duration: 0) {
        self.inner.points = self.randomize(self.inner.points, radius: self.radius.1)
        let maskPoints = self.randomize(self.innerMask.points, radius: self.radius.0)
        self.innerMask.points = maskPoints
        self.outer.points = maskPoints
    }.animate()
}

The animation is 0 seconds long!

This update() randomizes the inner points, randomizes the mask points, then sets both the mask and outer polygons to have maskPoints.

The randomize function looks like this:

func randomize(var points: [Point], var radius r: Double) -> [Point] {
    for i in 0..<points.count {
        if random(below: 1000) > 600  {
            r = distance(Point(), rhs: points[i]) * Double(random(min: 95, max: 105)) / 100.0
        }
        let θ = Double(i) / 45.0 * M_PI
        points[i] = Point(r * sin(θ), r * cos(θ))
    }
    return points
}

This takes a given set of points and a default radius. It then iterates through each point, and for ~40% of the time (i.e. anything over 600) it randomizes the radius. It calculates the current point’s distance to the shape’s center, then adds +/- 5%.

Then, it recalculates the new point based on the new radius and updates the points array.

Customizations

You can find a wide variation of aesthetics by changing only a few initial variables.

Here’s a list of class variables you can change:

  • var pointCount = ... The number of points in the shape
  • var radius = ... The radiuses for the shapes
  • var lineWidths = ... The line thicknesses
  • var primaryColor = ... The main color

You can also change the following variables in randomize():

  • if rand < 0.9 ... Adjusts the probability of a point being updated
  • random(min: 90, max: 110) Adjusts the percent increase or decrease for the point (this value is currently +/- 10%
  • let θ = ... Adjusts the calculated angle for the new point

Fire Burst

var pointCount = 90
var radius = (60.0, 50.0)
var lineWidths = (1.0, 0.5)
var primaryColor = orange

func randomize(points: [Point], radius: Double) -> [Point] {
    var newPoints = [Point]()
    for i in 0..<points.count {
        var r = radius
        let rand = random01()
        if rand < 0.9 {
            let percentage = Double(random(min: 90, max: 110)) / 100.0
            let d = distance(Point(), rhs: points[i])
            r = d * percentage
        }
        let θ = Double(i) / 45.0 * M_PI
        newPoints.append(Point(r * sin(θ), r * cos(θ)))
    }
    return newPoints
}

Pink

var pointCount = 90
var radius = (60.0, 50.0)
var lineWidths = (1.0, 0.5)
var primaryColor = C4Pink

func randomize(var points: [Point], var radius r: Double) -> [Point] {
    for i in 0..<points.count {
        if random(below: 10) > 4  {
            r = distance(Point(), rhs: points[i]) * Double(random(min: 95, max: 105))
            r /= 100.0
        }
        let θ = Double(i) / 45.0 * M_PI
        points[i] = Point(r * sin(θ), r * cos(θ))
    }
    return points
}

Rotating Emerald

var pointCount = 90
var radius = (60.0, 50.0)
var lineWidths = (1.0, 0.5)
var primaryColor = green

func randomize(var points: [Point], var radius r: Double) -> [Point] {
    for i in 0..<points.count {
        if random(below: 1000) > 600  {
            r = distance(Point(), rhs: points[i]) * Double(random(min: 95, max: 105))
            r /= 100.0
        }
        let θ = Double(i) / 45.0 * M_PI
        points[i] = Point(r * sin(θ), r * cos(θ))
    }
    return points
}

And, in setup() call animateCanvas():

override func setup() {
    canvas.backgroundColor = black
    createInner()
    createInnerMask()
    createOuter()
    positionShapes()
    initializeDisplayLink()
    animateCanvas()
}

Blue Spark

var pointCount = 90
var radius = (50.0,58.0)
var lineWidths = (2.0,0.5)
var primaryColor = C4Blue

func randomize(var points: [Point], var radius r: Double) -> [Point] {
    for _ in 0..<points.count {
        let index = random(below: points.count)
        if random(below: 10) > 6  {
            r = distance(Point(), rhs: points[index]) * Double(random(min: 95, max: 105)) / 100.0
        }
        let θ = Double(index) / 45.0 * M_PI
        points[index] = Point(r * sin(θ), r * cos(θ))
    }
    return points
}

This is the first one we created.

Fin

Try playing around with the settings to see what you come up wtih.

Code

You can grab a copy of the code for this project from here.

Bands


Let’s up our game now and get into complex interactions by creating a canvas for you to animate abstract patterns using your fingertips. We’re going to add 9 interactions to each pattern object, and two to the main canvas. With all these gestures kicking around we’ll have to be careful to balance the order and timing of their execution.

Before we get going, let’s have a look at what we’re going to build!

Concept

To cap off this intro to C4, we wanted to build an app that would allow you to create some trippy abstract animations. This app should be more than a simple sketch, it should allow you to add, animate, manipulate (i.e. move or rotate) and delete objects. It should also allow you to screen cap your apps and save them directly to your phone’s photos library. AND, it should do all that in a simple way.

You can also use the default preview to share your vids via message, twitter, email, etc.

Create a new Project

Start by creating a new C4 project called Bands.

The Bands

The main component of this app are the bands. A band is a type of shape that has eight distinct gestures, four of which create lines that animate across the face of the band. The other five gestures allow you to move, rotate, toggle its line / background, and delete the shape from the canvas.

B01

Swiping on a band will cause a line to start animating across the band in the direction of the swipe (Up, Down, Left, or Right).

B02

Holding down on a band for a short time will cause it to go into “move” mode, where you can drag it around the screen.

B03

You can rotate a band using a 2-finger rotation gesture.

B04

If you rotate a band fast enough it will start animating!

B05

Bands are opaque by default. If one is on top of another it will block the other’s content.

B06

If you tap a band, its outline will disappear.

B07

If you double-tap a band its background will clear.

B08

If you triple-tap a band it will disappear and remove itself from the canvas.

Right, let’s build it.

A Shell of a Band.swift

Let’s start out by building the band class. We’ll do this in steps so that you can demo your progress as you go along.

Create a new C4 project. Then, add a new file called Band.swift to your project. You can create a new file either by right-clicking on the project navigator and choosing New File ..., or hitting CMD+N.

newFile

In that new file you can copy and paste the following:

//import C4
import UIKit

public class Band: Rectangle {
    //We'll need access to the object's superview-canvas for the pan gesture
    var canvas: View!

    //Keeps track of the swipe gestures
    var swipes = [UISwipeGestureRecognizer]()

    //We need to enable / disable the rotation gesture, so we need a reference
    var rotationGesture: UIRotationGestureRecognizer?

    var rotationAnimation: ViewAnimation?

    public func setupIn(canvas: View) {
        //sets up the shape
    }

    func addRotation() {
        //rotates the band
    }

    func createRotationAnimation(multiplier: Double) {
        //animates a the rotation of the band
    }

    func animateLine(line: Line, to target: Point) {
        //animates a given line
    }

    func addSwipes() {
        //Four swipes will add lines
    }

    func createLine(points: (Point, Point), w: Double) -> Line {
        //creates a new animatable line
        return Line((Point(),Point()))
    }

    func addTaps() {
        //triple tap to remove the object
        //double tap to toggle the background
        //single tap to toggle the border
    }

    func addPan() {
        //moves the shape around the canvas
    }
}

This shell contains ALL of the function s and variables we will need to make a Band. As we go along we will fill in each of the functions… Starting with setupIn(canvas:).

Setup

Now, we’ll want to see a band pretty soon… So, we should style it. Modify setupInCanvas: to look like this:

public func setupIn(canvas: View) {
    self.canvas = canvas
    strokeColor = C4Blue
    fillColor = C4Grey
    corner = Size()
}

The important part here is that we save a reference to an object called canvas. The canvas is the view in which the band is going to exist – i.e. it’s superview.

WorkSpace

Now, to see something in action you can switch to WorkSpace.swift where you will add a function that creates a new band. You’ll apply the same process as with Band.swift by first adding the following functions:

import UIKit

class WorkSpace: CanvasController {
    //The object that will reccord the screen
    var recorder = ScreenRecorder()
    //The gesture to initiate recording, we need a reference to enable / disable dynamically
    var startRecording: UILongPressGestureRecognizer?

    override func setup() {
        //initializes the app
    }

    func createBand(center: Point, displacement: Vector) -> Band {
        //creates a band
        return Band()
    }

    func canvasLongPress() {
        //adds a longpress gesture to create a band
    }

    func canvasStartRecording() {
        //adds a longpress gesture to start recording
    }
}

Creating a Band

The only thing we need to worry about (for now) with the main WorkSpace is adding a gesture that will create a new band.

Update createBand(...) to the following:

func createBand(center: Point, displacement: Vector) -> Band {
    let w = max(abs(displacement.x), 8.0) * 2
    let h = max(abs(displacement.y), 8.0) * 2
    let f = Rect(center.x-w/2.0, center.y-h/2.0, w, h)
    let band = Band(frame: f)
    band.setupIn(self.canvas)
    return band
}

Here’s a breakdown of what’s happening in this createBand:

  1. This function takes a Point and a Vector.
  2. The displacement is a measure of how far a user’s finger is from the center point of the new band.
  3. Using the displacement you calculate the width (w) and height (h) variables.
  4. Using, the width and height you construct a frame (f) for the shape
  5. You initialize a new Band with the calculated frame.
  6. You setup the band passing the canvas as a reference.

The Long Press

You’ll use a longpress gesture recognizer to create new bands. The reason for this, over a tap, is that you can take advantage of the .Changed state of the long press to modify the shape in real-time.

Here’s a bit of UX logic:

  1. A user presses and holds down on the screen
  2. After a quarter of a second (0.25s) a band is created
  3. The center of the band is fixed to the original touch point of the gesture
  4. The user drags their finger around the canvas
  5. As their finger drags the new band’s shape changes based on the user’s finger position
  6. The new band can be updated so long as the user’s finger remains pressed
  7. The shape’s frame is finalized when the user lifts their finger, ending the gesture

And, here’s what that logic looks like in code:

func canvasLongPress() {
    var currentBand: Band?
    var position = Point()
    canvas.addLongPressGestureRecognizer { (locations, center, state) -> () in
        switch state {
        case .Began:
            ShapeLayer.disableActions = true
            position = center
            currentBand = self.createBand(position, displacement: Vector())
            self.canvas.add(currentBand)
        case .Changed:
            let dxdy = Vector(center) - Vector(position)
            let newBand = self.createBand(position, displacement: dxdy)
            currentBand?.removeFromSuperview()
            currentBand = newBand
            self.canvas.add(currentBand)
        case .Ended:
            ShapeLayer.disableActions = false
        default:
            _ = ""
        }
    }
}

Go ahead and modify the canvasLongPress in your WorkSpace to look like the above.

The Variables

Since the current band will be constantly updated we need to have a local reference to it. We also need to store the center point of the shape.

var currentBand: Band?
var center = Point()

The Long Press

The long press needs to be split up into 3 states, basically the beginning, middle and end. The syntax for the three states are: .Began.Changed.Ended.

.Began

This state gets triggered only once each time the user starts a new long press gesture. In this state you set the center variable to the location of the gesture, and then set the variable currentBand to a newly initialized Band. You then add the new band to the canvas.

You can pass a zero Vector (i.e. {0,0,0}) because the user hasn’t yet moved their finger.

case .Began:
    ShapeLayer.disableActions = true
    position = center
    currentBand = self.createBand(position, displacement: Vector())
    self.canvas.add(currentBand)

Finally, you’ll notice that we set the following: ShapeLayer.disableActions = true. This line tells C4 to create zero-second animations… That is, we want our bands to be created and styled instantaneously.

Technically, in Core Animation an animation is created using an action. Actions define dynamic behaviors for a layer. For example, the animatable properties of a layer typically have corresponding action objects to initiate the actual animations.

.Changed

This is where things happen. Any time the user moves their finger, after a longpress is initiated, the .Changed state will occur. This can happen dozens of times a second, which is why we previously disabled animations.

Every time the user’s finger moves we run the following logic: determine how far their finger is from the center of the shape, create a new band, remove the current band, update the current band to the new band, add the new band to the canvas.

case .Changed:
    let dxdy = Vector(center) - Vector(position)
    let newBand = self.createBand(position, displacement: dxdy)
    currentBand?.removeFromSuperview()
    currentBand = newBand
    self.canvas.add(currentBand)

An in-depth explanation of the Shape class is beyond the scope of this tutorial, so I won’t go into detail. However, it’s important to note that C4 is very fast, so creating a new shape on the fly instead of manipulating the path of the current band is an efficient process.

.Ended

When the user removes their finger from the screen we want to re-initiate the ability to create animations.

case .Ended:
    ShapeLayer.disableActions = false

Run it.

Now that you have a basic Band class, and the necessary interactions attached to the canvas, you can test it out.

To make sure the gesture gets created, update the setup in WorkSpace to look like:

override func setup() {
    canvasLongPress()
}

Go ahead and run the app.

Band.swift

Pop back over to the Band class, which is where you’ll be working for a while. The goal is to add 9 gestures in such a way that they don’t collide with one another, making it fairly easy for the user to manipulate each object in different ways. We’ll approach this task in the following order:

  1. Taps
  2. Swipes
  3. Pan
  4. Rotation
  5. Rotation Animation

Taps

There are 3 distinct tap gestures we will apply to each Band. They are:

  1. Single: toggles the visibility of the band’s border
  2. Double: toggles the opacity of the band’s background
  3. Triple: removes the object from the canvas

Add the following function to Band.swift:

func addTaps() {
    let tt = addTapGestureRecognizer { locations, center, state in
    }

    let dt = addTapGestureRecognizer { locations, center, state in
    }

    let t = addTapGestureRecognizer { locations, center, state in
    }
}

Then, add the following line of code to setupIn(canvas:):

addTaps()

At any point, if you don’t see your taps having an effect, it’s probably because you need to add the last line of code in the Band setup function.

Straightforward. There are 3 distinct taps, so we create 3 a separate gesture recognizer for each one.

Single Tap

The single tap gesture needs to toggle the visibility of the object’s border. This can be done by either changing its color to clear or by changing its width to 0.0. We’re going to go with the latter approach:

Modify the above function to include the following:

let t = addTapGestureRecognizer { locations, center, state in
    if self.lineWidth > 0.0 {
        self.lineWidth = 0.0
    } else {
        self.lineWidth = 1.0
    }
}

This uses the current lineWidth of the object to toggle its visibility.

Double Tap

The double tap gesture is fairly similar, however, instead of toggling a value we will toggle a color.

Modify the above function to include the following:

let dt = addTapGestureRecognizer { locations, center, state in
    if self.fillColor?.alpha == 0.0 {
        self.fillColor = C4Grey
    } else {
        self.fillColor = clear
    }
}

Triple Tap

This gesture should remove the object from the canvas. Instead of simply removing the object, we will create an animaiton that fades and shrinks it prior to its removal.

Modify the above function to include the following:

let tt = addTapGestureRecognizer { locations, center, state in
    let a = ViewAnimation(duration: 0.25) {
        self.transform.scale(0.1, 0.1)
        self.opacity = 0.0
    }
    a.addCompletionObserver {
        self.removeFromSuperview()
    }
    a.animate()
}

This step scales the object to 10% of its natural size while fading it out. When the animation completes – at which point the object is invisible – the animation then triggers the removal of the object.

Separating the Gestures

If you run the project on your phone you can create a few new bands and test out the taps. You will notice that the gestures all trigger at the same time. To fix this we need to add 2 lines of code.

Add the following to the above function:

tt.numberOfTapsRequired = 3
dt.numberOfTapsRequired = 2

This makes each gesture require a unique number of touches. By default all gestures st require 1 touch, which is why we don’t need to specify anything for st.

If you run this again you’ll notice that you can single tap without triggering the other gestures. However, a double tap will trigger both st and dt, and a triple tap will trigger all three cases.

We’ve separated the gestures by the number of touches, now we need to separate them by only allowing them to trigger if the others are not activated.

Add the following to the above function:

dt.requireGestureRecognizerToFail(tt)
t.requireGestureRecognizerToFail(dt)

These two lines imply the following:

  • The double tap will wait a fraction of a second to make sure it isn’t part of a triple tap. It will only execute if tt fails.
  • The single tap will wait a fraction of a second to make sure it isn’t part of a double tap. It will only execute if dt fails.

Finito. Now you can run the app and make a bunch of bands and toggle their states.

Remember to add addTaps() to setupIn(canvas:)

Swipes

The effect of a swipe on a band is to create a line that moves across the band in the direction of the swipe. So, to create this interaction we need to both create lines, animate lines, AND have each gesture (there are 4 of them) set up the line and animate it accordingly.

Creating Lines

Initializing a Line requires a tuple of Point structs. Since we’re going to create a lot of lines the function should return a Line object.

Modify the createLine function to look like:

func createLine(points: (Point, Point), w: Double) -> Line {
    let line = Line(points)
    line.lineCap = .Butt
    line.interactionEnabled = false
    line.lineWidth = w
    line.strokeColor = C4Blue
    return line
}

This takes a tuple, (Point, Point) and a width (which we use to style the line). It then constructs a line with the colors, sizing, etc., and returns it.

Animating Lines

Animating a line is trivial, it requires access to a line as well as the target position to which it will animate.

Modify the animateLine function to look like this:

func animateLine(line: Line, to target: Point) {
    let a = ViewAnimation(duration: 1.0) {
        line.center = target
    }
    a.repeats = true
    a.animate()
}

On their own, neither the create, nor animate functions do very little. Let’s add some swipes to see them in action.

Right Swipe

The default direction for a swipe is .Right, so we can start with this case.

Add the following block of code to the addSwipes function:

let w = 10.0
addSwipeGestureRecognizer { locations, center, state, direction in
    let points = (Point(-w/2.0, 0), Point(-w/2.0, self.height))
    let line = self.createLine(points, w: w)
    self.add(line)
    self.animateLine(line, to: Point(self.width+w/2.0, line.center.y))
}

The w defines the width of the line. If you want your lines to have a dynamic width you can modify this variable with some logic. For this exercise we will leave it at 10.0

This creates a line whose points lay vertically just off the left-side of the Band. It then animates the line by passing a new Point where the x position has changed.

For horizontal motion the x position changes, for vertical motion the y position changes.

The Left, Up, Down Swipes

The process for creating the other swipes is nearly identical. The only difference is that we apply a direction to each one.

Add the following block of code to the addSwipes function:

let up = addSwipeGestureRecognizer { locations, center, state, direction in
    let points = (Point(0, self.height+w/2.0), Point(self.width, self.height+w/2.0))
    let line = self.createLine(points, w: w)
    self.add(line)
    self.animateLine(line, to: Point(line.center.x, -w/2.0))
}
up.direction = .Up

let down = addSwipeGestureRecognizer { locations, center, state, direction in
    let points = (Point(0, -w/2.0), Point(self.width, -w/2.0))
    let line = self.createLine(points, w: w)
    self.add(line)
    self.animateLine(line, to: Point(line.center.x, self.height+w/2.0))
}
down.direction = .Down

let left = addSwipeGestureRecognizer { locations, center, state, direction in
    let points = (Point(self.width+w/2.0, 0), Point(self.width+w/2.0, self.height))
    let line = self.createLine(points, w: w)
    self.add(line)
    self.animateLine(line, to: Point(-w/2.0, line.center.y))
}
left.direction = .Left

Simple.

Now, run the app and try to swipe on a band or two.

Remember to add addSwipes() to setupIn(canvas:)

Pan

Moving an object around the screen requires a gesture that can track a user’s finger tip. There are two functions that do this: pan and longpress. Since we already have swipe gestures attached to our object the pan gesture won’t work for us… We need a little delay to make sure that none of the 4 swipes collide with the movement of the object. So, we use a longpress.

Modify the addPan function to look like this:

func addPan() {
    var dxdy = Vector()
    let lp = addLongPressGestureRecognizer { locations, center, state in
        switch state {
        case .Began:
            ShapeLayer.disableActions = true
            dxdy = Vector(center)
            self.strokeColor = C4Pink
        case .Changed:
            self.origin = self.canvas.convert(center, from: self) - dxdy
        case .Ended:
            self.strokeColor = C4Blue
            ShapeLayer.disableActions = false
        default:
            _ = ""
        }
    }
    lp.minimumPressDuration = 0.1
}

Here’s what’s going on:

  1. We create a dxdy variable to track the point of a user’s touch, inside a band, relative to the top-left corner of the shape.
  2. When the press begins, we disable implicit animations, set dxdy and change the object’s strokeColor to give a visual reference that the shape is in move mode.
  3. When the user drags their finger we update the origin of the shape using dxdy as a reference
  4. When the user is finished, we enable actions and set the strokeColor back to its original state.

Since we want to move the object within the canvas this is why we need to have a variable that stores the reference. If we tried to update the object based on its own coordinate system the shape would move out of control and very quickly zoom off screen.

Remember to add addPan() to setupIn(canvas:)

Rotation

Rotating an object is as straightforward as the last step.

Modify the addRotation function to look like this:

func addRotation() {
    rotationGesture = addRotationGestureRecognizer { rotation, velocity, state in
        switch state {
        case .Began:
            ShapeLayer.disableActions = true
        case .Changed:
            self.rotation = rotation
        case .Ended:
            ShapeLayer.disableActions = false
        default:
            _ = ""
        }
    }
}

Each time the gesture changes, the shape’s rotation gets updated.

Rotation Animation

Let’s add another dimension to this rotation. If the user rotates the shape fast enough it should start animating its rotation.

To do this we will need an animation and a bit of logic. Let’s start with the logic:

Modify the .Changed state of the rotation gesture to look like this:

case .Changed:
    self.rotation = rotation
    if abs(velocity) >= 10 {
        self.rotationGesture?.enabled = false
        self.createRotationAnimation(velocity / 10.0)
    }

This checks the current velocity of the gesture, and if it is greater than 10.0 we disable the gesture and start an animation based on the velocity’s value.

Disabling the gesture is critical here, because we only want to trigger the animaiton the first time its velocity passes the 10.0 threshold. Also, once its animating we don’t want the user to be able to rotate it again…

Next, modify the createRotationAnimation to look like this:

func createRotationAnimation(multiplier: Double) {
    ShapeLayer.disableActions = false
    let Θ = M_PI * 2.0 * (multiplier < 0.0 ? -1.0 : 1.0)

    let a = ViewAnimation(duration: 4.0 / abs(multiplier)) {
        self.rotation += Θ
    }
    a.repeats = true
    a.curve = .Linear
    a.animate()
}

This uses the multiplier, which should be between 1.0 and 3.0 (it’s hard to get a rotation velocity higher than 30), to calculate the rotation direction and the duration of the animation.

The rotation will either be - or + (clockwise or counterclockwise), so we use the following line to set the angle:

let Θ = M_PI * 2.0 * (multiplier < 0.0 ? -1.0 : 1.0)

This creates a full 360° rotation, and multiplies that by -1.0 or 1.0 depending on the value of the multiplier.

Depending on the value of the velocity the duration of the animation, 4.0 / abs(multiplier), will be somewhere between 4.0s and 1.33s (i.e. between 4/1.0 and 4/3.0).

Run it, and spin some things!

Remember to add addRotation() to setupIn(canvas:)

Back to the WorkSpace.swift

The last step in this section is to add a ScreenRecorder to the app. This will allow you to save recordings of the bands you create, you can even share them from the app’s preview dialog.

In WorkSpace.swift modify the canvasStartRecording function to look like this:

func canvasStartRecording() {
    startRecording = canvas.addLongPressGestureRecognizer { location, center, state in
        self.startRecording?.enabled = false
        let v = View(frame: self.canvas.frame)
        v.backgroundColor = C4Pink
        self.canvas.add(v)

        let a = ViewAnimation(duration: 0.25) {
            v.opacity = 0.0
        }

        a.addCompletionObserver {
            v.removeFromSuperview()
            self.recorder.start(10.0)
        }
        a.animate()
    }

    startRecording?.numberOfTouchesRequired = 3
}

This function does the following:

  1. Disables the start recording gesture
  2. Creates a white view the size of the canvas
  3. Flashes the white view to indicate the start of recording
  4. Starts recording the canvas for 5.0 seconds
  5. Specifies 3 touches are needed to start the recording

Finally, modify the setup function to look like this:

override func setup() {
    canvasLongPress()
    canvasStartRecording()

    self.recorder.recordingEndedAction = {
        self.recorder.showPreviewInController(self)
        self.startRecording?.enabled = true
    }

    let logo = Image("logo")!
    logo.constrainsProportions = true
    logo.width = 22.0

    logo.origin = Point(2, canvas.height-logo.height)
    logo.zPosition = 1000
    canvas.add(logo)
}

This does the following:

  1. creates the startRecording gesture
  2. adds a condition: when recording completes, show the recording in a preview and then enable the recoring gesture
  3. creates a little C4 logo and puts it in the bottom corner of the canvas.
  4. the zPosition = 1000 makes sure the logo is always above the other objects

Try it!

All you need to do now is try out the app, save a few vids, and share them if you want!

If you share via Twitter, please tag with @C4Framework so we can see what you’ve made!

Here are a few vids we created:

Code

You can grab a copy of the code for this project from here

The Beginning


This tutorial introduced you to creative-coding on iOS with C4 by introducing you to some key concepts, like: Properties, Layers, Animations and Gestures. From highly experimental interactive experiences to polished apps, you’ve now seen a variety of works that have been created using the framework.

Throughout the writing of this tutorial we were continuously adding new features like the locationsvariable in gestures, the ScreenRecorder and Camera objects. So, the code you’ve been writing is the most cutting-edge release of C4. But, it’s far from complete. Now that the new Swift version of the core API is stable, it’s time to start building out new components and addons. This is the beginning of a new chapter in C4’s growth and we’re really excited that you’ve been able to see where it is and what it can become.

To learn more about C4, please visit: www.c4ios.com

  • J Leyba

    The videos are all broken on this page.

  • The look OK here.