Chapter 12
Small Stars
Bring those constellations to life with stars. We're starting small.
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.
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.
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.
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:
Shape
TextShape
TextShape
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.
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.
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.
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 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)