Chapter 11
Sign Lines

Estimated Time


The next class we’re going to create is one that will position and add all the little purple lines that connect the stars in our constellations.

Open SignLines.swift and you’ll notice this class is also a simple subclass of InfiniteScrollview that has a few more properties than the previous class we built.

Concepts for this chapter are:

  1. We need an array of all the lines in the layer
  2. We need to know the current sign (e.g. index)
  3. We need to access the set of lines for the current sign

All the Lines

The big and small stars that make up an astrological sign are connected. To draw those lines we grab the connection points from each of the AstrologicalSign structs we’re working with and create an array of actual Line objects between any two connection points.

To start adding lines we need to create an array into which we’re going to store each set of lines (yes, we’re making an array of arrays).

Add the following property to your class:

1
var lines : [[Line]]!

The reason we need this property is because we will eventually animate these lines, so we need a reference to them.

Current Lines

Next, we need a way of grabbing the current set of lines that are on screen, which we can do by creating an index and overriding a property. Add the following to your class:

1
2
3
4
5
6
7
var currentIndex : Int = 0
var currentLines : [Line] {
    get {
        let set = lines[currentIndex]
        return set
    }
}

This is a nice little trick. Since we have a list of all the lines we don’t want to create copies if we don’t need to… So, instead of copying the current lines into the property, we simply make the property read-only (i.e. get) and return the current set based on the currentIndex.

Initialization

Like the previous class we built, this one only need an initializer to lay out its content. So, we’re going to add a bunch of logic to the init method.

You don’t have to worry at all about the init?(coder) method, it just needs to be in there to make Xcode happy.

Add the following after super.init():

1
2
3
4
5
let count = CGFloat(AstrologicalSignProvider.sharedInstance.order.count)
contentSize = CGSizeMake(frame.width * (count * gapBetweenSigns + 1), 1.0)

var signOrder = AstrologicalSignProvider.sharedInstance.order
signOrder.append(signOrder[0])

We now have our content size for the layer as well as an order of signs to add. Next, we populate the lines array and add everything to the canvas.

Add a for loop to your init like so:

1
2
3
4
5
6
7
8
lines = [[Line]]()
for i in 0..<signOrder.count {
    let dx = Double(i) * Double(frame.width * gapBetweenSigns)
    let t = Transform.makeTranslation(Vector(x: Double(center.x) + dx, y: Double(center.y), z: 0))
    if let sign = AstrologicalSignProvider.sharedInstance.get(signOrder[i]) {
        //Layout the lines here
    }
}

We grab the signs (in order) and step through a loop that creates a displacement (dx) and a transform (t), both of which we’ll use to position the lines. Finally, we grab the current AstrologicalSign and get ready to build out the lines.

Now, in the place where we have //Layout the lines here, we want to add the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let connections = sign.lines
var currentLineSet = [Line]()
for points in connections {
    var begin = points[0]
    begin.transform(t)
    var end = points[1]
    end.transform(t)
    
    let line = Line((begin,end))
    line.lineWidth = 1.0
    line.strokeColor = COSMOSprpl
    line.opacity = 0.4
    line.strokeEnd = 0.0
    
    add(line)
    currentLineSet.append(line)
}
lines.append(currentLineSet)

This step starts by grabbing the connections for the current sign. Then, it creates an empty array to hold the lines for the current sign, and then iterates over the connections to build each of the lines.

To build each line the code grabs the begin and end points of the current connection and applies the previously calculated transform (t). Then, it styles the line and adds it to both the scrollview and the current line set. Finally, it adds the currentLineSet to the entire list of sign lines.

Animations

The design of our app specifies that the current set of lines should animate in after snapping, and out as soon as the user starts scrolling. To do this, we’re going to build two methods for these animations.

It’s quite simple, just add the following to your class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func revealCurrentSignLines() {
    ViewAnimation(duration: 0.25) {
        for line in self.currentLines {
            line.strokeEnd = 1.0
        }
    }.animate()
}

func hideCurrentSignLines() {
    ViewAnimation(duration: 0.25) {
        for line in self.currentLines {
            line.strokeEnd = 0.0
        }
    }.animate()
}

C’est tout.

Grab the SignLines.swift gist.

Test it.

To test the layout of our lines, run the following in your project’s WorkSpace:

1
2
let linesLayer = SignLines(frame: view.frame)
canvas.add(linesLayer)

…and make sure you either comment out the following in SignLines:

1
//line.strokeEnd = 0.0

If you scroll you’ll be able to see all the different sign shapes. Here are some samples:

Remember to undo the the last couple test changes before moving on.