Chapter 7
Astrological Sign Provider - Build it!

Estimated Time


Building the AstrologicalSignProvider is going to be pretty straightforward. We’re going to construct the basic elements and then fill in the contents for a single sign. Then, I’ll give you a copy of the final file because the rest of the 11 signs simply need to have their content copy-pasted (so there’s no point in doing that if I already have that file made).

You are going to do the following:

  1. Create a new subclass of NSObject called AstrologicalSignProvider
  2. Create a structure called AstrologicalSign that holds data for a single sign
  3. Create a typealias that represents a method that creates an AstrologicalSign
  4. Create a set of mappings between strings and a type alias for each of the signs
  5. Create a get method that allows you to grab the data you need by requesting the name of the sign
  6. Create a method for each of the signs that returns the data for that sign

From now on, we’re going to be working in AstrologicalSignProvider.swift

Create a Struct

Above the class declaration, create a struct called AstrologicalSign that has 4 variables called shape, big, small and lines, like so:

1
2
3
4
5
6
7
8
struct AstrologicalSign {
    var shape : Shape!
    var big : [Point]!
    var small : [Point]!
    var lines : [[Point]]!
}

class AstrologicalSignProvider : NSObject {...

This struct will give us access to a Shape (the icon), two arrays of points for the centers of big and small stars, as well as an array of arrays of points that lets us know between which stars we’ll draw lines.

Next, create a typealias called AstrologicalSignFunction which defines a kind of function that returns a sign struct. Add it above AstrologicalSign like so:

1
2
3
typealias AstrologicalSignFunction = () -> AstrologicalSign

struct AstrologicalSign {...

Finally, add a fixed array of strings as an immutable let to the class. This array represents the order of astrological signs.

1
let order = ["pisces", "aries", "taurus", "gemini", "cancer", "leo", "virgo", "libra", "scorpio", "sagittarius", "capricorn", "aquarius"]

Singleton

We’re going to be referencing the provider from a variety of different classes, but the nature of the provider will never change. Instead of having a local instance of the provider in each different class, we’re going to create a sharedInstance – a singleton – that gets created only once. This instance will be accessible from any object in our project.

Add this property to the class:

1
static let sharedInstance = AstrologicalSignProvider()

At this point your file should look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation

typealias AstrologicalSignFunction = () -> AstrologicalSign

struct AstrologicalSign {
    var shape : Shape!
    var big : [Point]!
    var small : [Point]!
    var lines : [[Point]]!
}

class AstrologicalSignProvider : NSObject {
    static let sharedInstance = AstrologicalSignProvider()
    
    let order = ["pisces", "aries", "taurus", "gemini", "cancer", "leo", "virgo", "libra", "scorpio", "sagittarius", "capricorn", "aquarius"]

    override init() {
        super.init()
    }
}

Fake all the sign methods

Next, we’re going to create all the sign methods but not fill them in with anything. We’re going to fake them so that we can first build the rest of our class before filling them in. It’s easier this way…

Trust me.

For each of the 12 signs, create a method with the pattern:

1
2
3
4
func signName() -> AstrologicalSign {
   let sign = AstrologicalSign()
   return sign
}

And, for each of the 12 signs replace the word signName with the name of the sign. Aries looks like:

1
2
3
func aries() -> AstrologicalSign {
    ...
}

By now your class should look like:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import C4

typealias AstrologicalSignFunction = () -> AstrologicalSign

struct AstrologicalSign {
   var shape = Shape()
   var big = [Point]()
   var small = [Point]()
   var lines = [[Point]]()
}

class AstrologicalSignProvider : NSObject {
   let order = ["pisces", "aries", "taurus", "gemini", "cancer", "leo", "virgo", "libra", "scorpio", "sagittarius", "capricorn", "aquarius"]

   func taurus() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func aries() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func gemini() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func cancer() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func leo() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func virgo() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func libra() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func pisces() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func aquarius() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func sagittarius() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func capricorn() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }

   func scorpio() -> AstrologicalSign {
       let sign = AstrologicalSign()
       return sign
   }
}

Create the Mappings

The next thing we want to do is create a set of mappings that will allow us to “get” a sign simply by requesting it by name. This step is where we start using our typealias that we created a couple of steps ago.

Add the following after the let order = [...] property:

1
internal var mappings = [String : AstrologicalSignFunction]()

There is a really nice feature of Swift that allows us to put functions into arrays and dictionaries, which we’re going to take advantage of shortly. The internal keyword means that the variable can’t be accessed from outside the class (i.e. it’s only available internally).

init() them

The next step is to initialize the mappings. So, modify the init method for the class and set the keys and variables for the mappings. All you need to do is for each of the signs, add its name and associate it with its function, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
override init() {
   super.init()
   mappings = [
   "pisces":pisces,
   "aries":aries,
   "taurus":taurus,
   "gemini":gemini,
   "cancer":cancer,
   "leo":leo,
   "virgo":virgo,
   "libra":libra,
   "scorpio":scorpio,
   "sagittarius":sagittarius,
   "capricorn":capricorn,
   "aquarius":aquarius
   ]
}

You can add this method after the mappings variable

A little bit of explanation…

So, we’ve done a few things that probably don’t make much sense, which is just fine because the class doesn’t actually do anything yet. However, we’ve also do a lot despite the class not doing anything.

This is what we’ve done:

  1. We’ve created an AstrologicalSign struct to represent signs (but haven’t really used them yet)
  2. We’ve created a type alias that represents functions
  3. We’ve created 12 functions that return sign structs
  4. We’ve created a dictionary of mappings that associates each of the functions with their string-based name (which we’ll use shortly)
  5. We’ve created an initializer that builds our mappings

From here, we’ll create a method that allows us to get a sign by passing our class a name. We’ll follow up with filling in all the empty sign methods.

Get() it

To get a sign we want to pass the name of the sign to our class. Why? Because it makes our code cleaner throughout the rest of our project, and it’s easier to read so we know what’s going on.

The get method is simple, it looks like this:

1
2
3
4
func get(sign: String) -> AstrologicalSign? {
    let function = mappings[sign.lowercaseString]
    return function!()
}

This method does something nice, it retrieves a method based on the name of the sign being passed, then instead of returning the method it runs the method and returns it (i.e. the () of the return statement is the part that runs the function, and the ! is the part that first gives us access to the raw value of the returned closure).

So, from now on we can call (from any other class) something like the following:

1
signProvider.get("aries")

Which will return an AstrologicalSign with the data for Aries.

Slick.

Fill in the methods

Let’s fill in the first method, and leave the rest for a good copy/paste job.

First, let’s add a path to the aries() method.

1
let bezier = Path()

Even though this variable is a let we’ll still be able to add points, curves and lines to it.

To create the aries icon, add the following after creating the bezier:

1
2
3
4
5
6
7
8
9
10
11
bezier.moveToPoint(Point(2.8, 15.5))
bezier.addCurveToPoint(Point(0, 9), control1:Point(1.1, 13.9), control2:Point(0, 11.6))
bezier.addCurveToPoint(Point(9, 0), control1:Point(0, 4), control2:Point(4, 0))
bezier.addCurveToPoint(Point(18, 9), control1:Point(14, 0), control2:Point(18, 4))
bezier.addLineToPoint(Point(18, 28.9))
        
bezier.moveToPoint(Point(18, 28.9))
bezier.addLineToPoint(Point(18, 9))
bezier.addCurveToPoint(Point(27, 0), control1:Point(18, 4), control2:Point(22, 0))
bezier.addCurveToPoint(Point(36, 9), control1:Point(32, 0), control2:Point(36, 4))
bezier.addCurveToPoint(Point(33.2, 15.5), control1:Point(36, 11.6), control2:Point(34.9, 13.9))

These method calls construct the shape of Aries into the bezier we just created.

Next, we’ll create the stars (big and small). Add the following:

1
2
3
4
5
6
let big = [
   Point(58.0,-57.7),
   Point( )]
let small = [
   Point(-134.5,-95.2),
   Point(137.0,11.3)]

Next, we’ll add an array of arrays :)…

1
2
3
4
let lines = [
   [big[0],small[0]],
   [big[1],big[0]],
   [big[1],small[1]]]

This array takes points from the previous two sets of arrays, so the first entry is actually:

1
[Point(58.0,-57.7), Point(-134.5,-95.2)]

We could have used tuples, but didn’t because creating a Line requires passing an array of points. And, because of this structure we can now create lines by writing things like:

1
let l = Line(lines[0])

Now, to finish off this method we need to populate the sign we’re going to return. Since we’ve already created it all we need to do is add the following before the return statement:

1
2
3
4
sign.shape = Shape(bezier)
sign.big = big
sign.small = small
sign.lines = lines

By now we’ve added a lot of lines to the aries() method which should now look like:

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
func aries() -> AstrologicalSign {
    let bezier = Path()
    
    bezier.moveToPoint(Point(2.8, 15.5))
    bezier.addCurveToPoint(Point(0, 9), control1:Point(1.1, 13.9), control2:Point(0, 11.6))
    bezier.addCurveToPoint(Point(9, 0), control1:Point(0, 4), control2:Point(4, 0))
    bezier.addCurveToPoint(Point(18, 9), control1:Point(14, 0), control2:Point(18, 4))
    bezier.addLineToPoint(Point(18, 28.9))
    
    bezier.moveToPoint(Point(18, 28.9))
    bezier.addLineToPoint(Point(18, 9))
    bezier.addCurveToPoint(Point(27, 0), control1:Point(18, 4), control2:Point(22, 0))
    bezier.addCurveToPoint(Point(36, 9), control1:Point(32, 0), control2:Point(36, 4))
    bezier.addCurveToPoint(Point(33.2, 15.5), control1:Point(36, 11.6), control2:Point(34.9, 13.9))
    
    var sign = AstrologicalSign()
    sign.shape = Shape(bezier)
    
    let big = [
        Point(58.0,-57.7),
        Point(125.5,-17.7)]
    sign.big = big
    
    let small = [
        Point(-134.5,-95.2),
        Point(137.0,11.3)]
    sign.small = small
    
    let lines = [
        [big[0],small[0]],
        [big[1],big[0]],
        [big[1],small[1]]]
    sign.lines = lines
    
    return sign
}

Last But Not Least

Instead of copy/pasting everything yourself from a long Numbers file, like I did, just grab the file:

AstrologicalSignProvider.swift

And, test it by heading back to your project’s WorkSpace and overriding setup() to look like:

1
2
3
4
5
override func setup() {
   let provider = AstrologicalSignProvider()
   let aries = provider.get("aries")
   print(aries)
}

Which prints the following to the console:

1
Optional(COSMOS.AstrologicalSign(shape: <COSMOS.Shape: 0x7fb1c0e0a280>, big: [{58.0, -57.7}, {0.0, 0.0}], small: [{-134.5, -95.2}, {137.0, 11.3}], lines: [[{58.0, -57.7}, {-134.5, -95.2}], [{0.0, 0.0}, {58.0, -57.7}], [{0.0, 0.0}, {137.0, 11.3}]]))

And, yes… That’s dandy.

Remember to delete the code in WorkSpace, it was only for testing.