Chapter 13
Big Stars

Estimated Time


One last time with the InfiniteScrollView subclass… But, this one’s finally going to be a bit of work because the top layer of the background has some extra flourishes to make it pretty.

Add the Stars

Since we’ve done this process a couple times already, just copy and paste this into your StarsBig.swift file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public override init(frame: CGRect) {
    super.init(frame: frame)
   
    var signOrder = AstrologicalSignProvider.sharedInstance.order
    contentSize = CGSizeMake(frame.size.width * (1.0 + CGFloat(signOrder.count) * gapBetweenSigns), 1.0)
    signOrder.append(signOrder[0])
    
    for i in 0..<signOrder.count {
        let dx = Double(i) * Double(frame.size.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]) {
            for point in sign.big {
                let img = Image("7bigStar")!
                var p = point
                p.transform(t)
                img.center = p
                add(img)
            }
        }
    }
}

Easy.

Add Dashes & Markers

The dashes and markers we want to add look like this:

There are a series of short dashes. Then, at the center of every astrological sign there is a tall white marker.

For the blue dashes, if we were blunt we might create a small dash and copy / paste that over… But, we’re much more elegant than that aren’t we?

To efficiently create the effect of a ton of dashes at the bottom of the screen, we’re going to create a line with a specific dash pattern that gives the effect we’re looking for.

You can read through the following steps and simply copy the entire method later.

Create a method for adding the dashes and the markers:

1
2
func addDashes() {
}

We start by creating a set of points that we’ll use to define 2 lines: a line for short dashes and a line for tall dashes. The points are:

1
let points = (Point(0,Double(frame.maxY)),Point(Double(contentSize.width),Double(frame.maxY)))

Which is essentially a line that is the same width as the entire width of a given scrollview.

To create the short dashes we create a Line and style it like this:

1
2
3
4
5
6
7
let dashes = Line(points)
dashes.lineDashPattern = [2,2]
dashes.lineWidth = 10
dashes.strokeColor = COSMOSblue
dashes.opacity = 0.33
dashes.lineCap = .Butt
add(dashes)

The dashes.lineDashPattern we specify represents a 2pt dash, followed by a 2pt gap which gets repeated for the entire length of the line. Adding the two values together we get a repeating pattern of dash and gap that is 4pt wide.

To create the markers we do the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func addMarkers() {
    for i in 0..<AstrologicalSignProvider.sharedInstance.order.count + 1 {
        let dx = Double(i) * Double(frame.width * gapBetweenSigns) + Double(frame.width / 2.0)

        let begin = Point(dx,Double(frame.height-20.0))
        let end = Point(dx,Double(frame.height))
        
        let marker = Line((begin,end))
        marker.lineWidth = 2
        marker.strokeColor = white
        marker.lineCap = .Butt
        marker.opacity = 0.33
        add(marker)
    }
}

Finally, at the end of setup we call these two methods and we’re done like dinna.

The + 1 in the for loop makes sure we have a marker in the overlapping “13th” frame.

Sign Names

Creating the sign names and small icon is fairly straightforward, we’re going to rely on the sign provider and a bit of math to position the various text shapes we’ll add to the canvas.

Again, I’ll walk you through the concepts, then you can just copy the final methods into your project.

To start we’re going to break down the problem into 4 simple steps:

  1. Create a small icon as a Shape
  2. Create a small sign title as a TextShape
  3. Create a small degree as a TextShape
  4. Position everything

Small Sign

To create a small sign we will do the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func createSmallSign(name: String) -> Shape? {
    var smallSign : Shape?
    //try to extract a sign from the provider, and style it
    if let sign = AstrologicalSignProvider.sharedInstance.get(name)?.shape {
        sign.lineWidth = 2
        sign.strokeColor = white
        sign.fillColor = clear
        sign.opacity = 0.33
        //scale the sign down from its original size
        sign.transform = Transform.makeScale(0.66, 0.66, 0)
        smallSign = sign
    }
    return smallSign
}

We create a method that takes a string, which should be the name of the astrological sign we are creating. The method then tries to grab an AstrologicalSign from the signProvider for the given name. If the name is spelled properly the following line gives us access to a copy of the current sign’s icon:

1
2
if let sign = AstrologicalSignProvider.sharedInstance.get(name)?.shape {
}

We then style the icon and scale it by applying a transform which reduces the original size by 33%. Then we return a copy of the styled shape.

Sign Title

To create the title we do the following:

1
2
3
4
5
6
7
func createSmallSignTitle(name: String, font: Font) -> TextShape {
    let text = TextShape(text:name, font:font)
    text.fillColor = white
    text.lineWidth = 0
    text.opacity = 0.33
    return text
}

This simply takes a title and a font and creates a label for us.

Sign Degree

This step is even easier:

1
2
3
func createSmallSignDegree(degree: Int, font: Font) -> TextShape {
   return createSmallSignTitle("\(degree)°", font: font)
} 

This simply creates a “name” for a title based on a specified degree (Int) value, and then uses the previous method to create the label.

Position Everything

With our 3 methods for creating signs and labels ready, all we need to do is set up a method that can iterate over the names and positions of each astrological sign.

We start by creating a method that takes a scrollview as input and creates a bunch of default values that will be used in each iteration:

1
2
3
4
5
6
7
8
9
10
11
12
13
func addSignNames() {
    var signNames = AstrologicalSignProvider.sharedInstance.order
    signNames.append(signNames[0])
    
    let y = Double(frame.size.height - 86.0)
    let dx = Double(frame.size.width * gapBetweenSigns)
    let offset = Double(frame.size.width / 2.0)
    let font = Font(name:"Menlo-Regular", size: 13.0)!
    
    for i in 0..<signNames.count {
        //set up each set of signs and labels here
    }
}

This method grabs the names of the signs, and appends the first to the end of the list (just like we did for the small and big stars).

Then it creates a y position which is where we’ll center each of the signs, a displacement (dx) and an offset that will be used to place the signs and labels based on the ordered position of a specific sign. It creates a font that will be used over and over again, and then sets up a loop to iterate through all the sign names.

The code for setting up each of the signs and labels looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let name = signNames[i]

var point = Point(offset + dx * Double(i),y)
if let sign = self.createSmallSign(name) {
    sign.center = point
    add(sign)
}

point.y += 26.0

let title = self.createSmallSignTitle(name, font: font)
title.center = point

point.y+=22.0

var value = i * 30
if value > 330 { value = 0 }
let degree = self.createSmallSignDegree(value, font: font)
degree.center = point

add(title)
add(degree)

It grabs the current sign’s name and calculates a center point. Then it tries to create a sign icon, and if successful it enters it to the point and adds it to the scrollview.

Then, it increases the point’s position by 26 and creates the title label for the current sign, centers it and adds it to the scrollview.

Then, it increases the point’s position by 22 and determines the current degree value for the sign. If the sign is the 13th, its degree value should be 360 (i.e. with i being 12), we actually want the value to be 0 (so that it overlaps with the first label). We then create the degree label, center it and add it to the scrollview.

All together your code for adding signs should look like this:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
func addSignNames() {
    //grabs the sign names
    var signNames = AstrologicalSignProvider.sharedInstance.order
    //appends a copy of the first name to the end of the array
    signNames.append(signNames[0])
    
    //specify the y position of the sign
    let y = Double(frame.size.height - 86.0)
    //calculate the displacement to the current frame
    let dx = Double(frame.size.width * gapBetweenSigns)
    //define the offset to the center of the canvas
    let offset = Double(frame.size.width / 2.0)
    //create a font
    let font = Font(name:"Menlo-Regular", size: 13.0)!
    
    //for each of the names
    for i in 0..<signNames.count {
        //grab the current
        let name = signNames[i]

        //calculate the point for the sign
        var point = Point(offset + dx * Double(i),y)
        //grab the current sign (based on the name), add it to the view
        if let sign = self.createSmallSign(name) {
            sign.center = point
            add(sign)
        }

        //offset y by a bit
        point.y += 26.0

        //add a label for the current name
        let title = self.createSmallSignTitle(name, font: font)
        title.center = point

        //offset y by a little bit
        point.y+=22.0

        //calculate the current degrees
        var value = i * 30
        //if it is > 330, make it 0 so the the overlap is consistent with the first sign's label
        if value > 330 { value = 0 }
        //create a label for the degrees
        let degree = self.createSmallSignDegree(value, font: font)
        degree.center = point

        add(title)
        add(degree)
    }
}

You can copy/paste that entire code block into your project.

Now, to add the signs and names, add the following at the end of the init method, like so:

1
2
3
4
5
6
public override init(frame: CGRect) {
    //…
    addDashes()
    addMarkers()
    addSignNames()
}

Voilà.

Download a copy of StarsBig.swift

Test it.

Test this if you want to see what the big stars look like with their pretty dashes and signs:

1
2
3
canvas.backgroundColor = COSMOSbkgd
let bigStars = StarsBig(frame: view.frame)
canvas.add(bigStars)