Chapter 2
Designing the COSMOS

Estimated Time
10 - 20 hours


Chapter 2: Designing the COSMOS

March 11th, I have an offer on the table to write a tutorial and have it published on a renown site. So, I start talking with Jake about coming up with possible concepts we can design, build and publish. Starting to get a grip on the new Swift-based C4, learning how to build animations with the new system… Thinking: lots of basic animations coming together into a nice, elegant interface. Watch a lot of concept UI videos. Brainstorm.

Jake gets an idea:

…where it starts as one filled circle in the middle, when you tap it, it shrinks a bit (90% size), and then shoots out 8 circles from that central point where all those circles create a bigger circle of “sub menus” which are just outlines of circles with different icons in each circle. Then, tapping an X in the centre will pull them all back into the original state.

Watch more videos.

Talk about the concept.

It works.

Run with it.

That’s how we roll.

Mocks & Tests

The actual app is simple, there are a lot of subtle elements in the interface and background that will take some time to get just right. Also, because the app is simple – yet complex – it will make for a good set of tutorials on how to build it from end-to-end using C4.

Jake presents his concepts for a single-view app composed of a brilliant animated menu and a layered parallax background that holds all the content. I have a look at both and in my head do a tear-down of how both components will be composed.

Background

The background is the easy one to decompose, mainly because it’s just a bunch of layers with different content moving at different speeds.

There are:

  • Big stars
  • Small stars
  • Lines connecting the stars
  • 3 layers of background stars
  • 2 layers of nebulas (for texture)

This is totally possible, so after a chat with Jake I come up with a list of things I need defined from him for this part:

  • Degrees / indicator animation
  • Individual Constellations
  • 3-layer foreground style + movements
  • 3-layer STAR background style + movements (incl. # of stars)
  • 2-layer Nebula background style + movements

My first step at this point is to test the number of layers I can get doing parallax at the same time… We need 8, so I just test with 10 to make sure it will work.

The code in this chapter represents the tests I made prior to deciding to move forward. So, when you’re done with the chapter, remember to delete everything you’ve added to the workspace file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class WorkSpace: CanvasController {
    //creates an empty variable array to which we'll add layers
    var layers = [UIScrollView]()

    override func setup() {
        //loops the code while the number of layers in our array is less than 10
        repeat {
            //creates a layer whose frame is the same as the canvas
            let layer = UIScrollView(frame: view.frame)
            //sets the content size for each layer, keeping 0 for height to prevent vertical scrolling
            layer.contentSize = CGSizeMake(layer.frame.size.width * 10, 0)
            //add the layer to the canvas and to the array
            canvas.add(layer)
            layers.append(layer)
        } while layers.count < 10
    }
}

Pretty straightforward. I’m working from the same project as the previous chapter, and in the project’s WorkSpace, I add a repeating loop that creates a new layer and adds it to the canvas until there are 10 layers. As each is being created, I make sure to set the layer’s contentSize to something quite large (in this case 20 times the width of the canvas). Setting the size’s height value to 0 will make sure it doesn’t scroll vertically.

At this point, if I run the app I’ll see nothing, so I modify the loop to include adding labels to each layer.

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
29
30
31
32
33
34
35
class WorkSpace: CanvasController {
    //creates an empty variable array to which we'll add layers
    var layers = [InfiniteScrollView]()

    override func setup() {
        //loops the code while the number of layers in our array is less than 10
        repeat {
            //creates a layer whose frame is the same as the canvas
            let layer = InfiniteScrollView(frame: view.frame)
            //sets the content size for each layer, keeping 0 for height to prevent vertical scrolling
            layer.contentSize = CGSizeMake(layer.frame.size.width * 10, 0)
            //add the layer to the canvas and to the array
            canvas.add(layer)
            layers.append(layer)

            //create a variable center point which will be used to position the labels
            var center = Point(24,canvas.height/2.0)
            //calculate the layer number (since we're adding the last layer first we start with 10 and work downwards)
            let layerNumber = 10 - layers.count
            //create a font whose size is based on the current layer count
            let font = Font(name: "AvenirNext-DemiBold", size:Double(layers.count+1) * 8.0)!
            //create a loop that runs until each layer is full of labels
            repeat {
                //create a label
                let label = TextShape(text: "\(layerNumber)", font: font)!
                //center it
                label.center = center
                //update the center point's position
                center.x += 130.0
                //add the label to the layer
                layer.add(label)
            } while center.x < Double(layer.contentSize.width)
        } while layers.count < 10
    }
}

This modifies the original setup to include a nested repeat that runs until the entire content size of the layer is filled with labels – with each label numbered based on the current layer.

Now, when the app runs there are labels. But! If I scroll the app only one layer moves…

The next step is to create an observer that looks at the top layer and moves all the rest when it is being scrolled. So, in setup just after the end of the while, I add the following:

1
2
3
4
5
6
if let top = layers.last {
    //creates a variable context
    var c = 0
    //adds the WorkSpace as an observer of the top layer's contentOffset
    top.addObserver(self, forKeyPath: "contentOffset", options: NSKeyValueObservingOptions.New, context: &c)
}

This bit sets up the WorkSpace as observer of the top layer’s contentOffset. Now, to make things pretty, I create a function that will respond to the movement of the layer and change the other ones accordingly. Like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    //iterates through all the layers, stopping 1 before the top layer
    for i in 0..<layers.count-1 {
        //grabs the current layer
        let layer = self.layers[i]
        //creates a mod value based on the layer's position (layer 0 = 0.1, layer 1 = 0.2, ...)
        let mod = 0.1 * CGFloat(i+1)
        //grabs the x value of the top layer's content offset
        if let x = layers.last?.contentOffset.x {
            //sets the content offset of the current layer by multiplying x by mod
            layer.contentOffset = CGPointMake(x*mod,0)
        }
    }
}

Pretty. Now we know that 10 layers will definitely work… But, what about when there’s a ton of media in them?… Time to test that. Jake ballparks the number of stars per “view” per layer at 15, and gives me a small white star.

I then swap the internal repeat loop to a for that adds images rather than labels. It looks like this now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//instead of center position, I simply add 10 * 15 stars per layer
let starCount = layers.count * 15
canvas.backgroundColor = black
//loop until there starCount stars in the layer
for _ in 0..<starCount {
    //create an image for the star
    let img = Image("6smallStar")!
    //allow it to scale proportionately
    img.constrainsProportions = true
    //scale the width of the image
    img.width *= 0.1 * Double(layers.count+1)
    //center it at a random point in the layer
    img.center = Point(Double(layer.contentSize.width)*random01(),canvas.height*random01())
    //add it to the layer
    layer.add(img)
}

And, the simulator looks good:

I try it on an iPhone 5 and it runs just fine. The 10-layer test works, so the only remaining bits to work out are aesthetics. And, by this point Jake has basically specified everything on the lists at the top of the Background section.

Individual Constellations

Each of the 12 astrological signs will be made up of 3 visual elements: big stars, small stars and lines connecting the shapes. This is the image that jake used to trace the positions of each kind of star:

See the big and small stars

Three-layer foreground style + movements

The next thing to define is “how” the stars and lines move in the foreground layers. Jake’s idea is to have the stars shift into place, so we decide to make 3 layers one for each of the big / small stars and one for the lines. When the app snaps into place for any given sign, the current stars should line up in the right positions. Then, right as everything snaps, there’s a super short animation of the lines between stars drawing from one star to another.

Three-layer STAR background style + movements

Next, I get jake to define how the stars move in the background. This step is pretty simple, he thinks its something like 5%, 15% and 20% of the speed of topmost layer. He also makes some ballpark guesses on how many stars per layer.

Two-layer Nebula background style + movements

Next, I get Jake to define how the nebula and vignette are going to look and move. This step is even easier than the previous one because the vignette simply doesn’t move, and the nebula layer moves at 10% speed.

Degrees / indicator animation

Finally, there will be a dashed line at the bottom of entire window, with a longer dash every 20 dashes. Then, when each individual constellation is centered there will be an even longer white line positioned under a graphic symbol of the constellation and its position (in degrees).

Initial layer specifications, which we modified on the fly after looking at a running version of the layers.

Finally

The last step before moving on is to have a list of the different layers that I’m going to be building. In his infinite kindness, Jake sends me this:

The menu “looks” pretty straightforward, except that it isn’t. However, the only real thing that I need to figure out is how we’re going to animate the astrological signs.

Jake wanted to use this diagram as the basis for the shapes of the astrological signs.

Actually, animating them is the easy part, but making them is trickier because we want them to be bezier paths but constructing them is a pain because we don’t know the curve points and applications like Illustrator don’t give us access to those. Also, I don’t want to write an SVG importer because that’s overkill…

So, what do we do?

We use PaintCode to draw out trace the shapes and then export their curvatures to Core Graphics code which looks like this:

1
2
3
4
5
6
UIBezierPath* bezier2Path = UIBezierPath.bezierPath;
[bezier2Path moveToPoint: CGPointMake(250, 200)];
[bezier2Path addLineToPoint: CGPointMake(150, 200)];
[bezier2Path addCurveToPoint: CGPointMake(100, 150) controlPoint1: CGPointMake(122.4, 200) controlPoint2: CGPointMake(100, 177.6)];
...
[bezier2Path closePath];

When I translate to code that looks like this:

1
2
3
4
5
6
let bezier = Path()

bezier.moveToPoint(Point(250,200))
bezier.addLineToPoint(Point(150,200))
bezier.addCurveToPoint(Point(100,150), control1:Point(122.4,200), control2:Point(100,177.6))
...

Things start to get more clear, and much easier to work with. Now that I have a process for getting the shapes of the astrological signs into C4 code it takes little effort to animate them the way we intend.

For example, letting a shape draw itself in is as simple as specifying:

1
shape.strokeEnd = 1.0

Redlines

At this point I’m ready to start building out the menu, but to do so I need redlines – specifications on position, size, etc., – for all the elements in the menu.

Jake does a lovely job of prepping this for me:

All the lovely details a coder needs for the menu

Time To Go

With the basic visual concepts specified, and the more complicated or questionable bits tested, it’s time to move on to some real dev work. But, before we get going I’ll sum up the problems I know I’ll have to address:

  1. Custom shapes – I will be animating and reusing many shapes throughout the app, so I should build them with custom bezier paths rather than importing assets.
  2. Complex Animation Sequences – There will be a complex series of animation sequences and timing to get the right feel for the menu expanding and contracting.
  3. Defining the interaction through gestures – I’ll want to keep the gestures for interacting as simple as possible, while making them unique.
  4. Parallax + Infinite Scrollview – adding parallax to the app is a must, so I’ll need to be careful that performance stays relatively high when the app is produced.

MAKE SURE TO DELETE ANY TEST CODE YOU’VE INCORPORATED INTO WorkSpace.swift… You should only have an empty setup().

Let’s go.