• Install
  • Tutorials
  • COSMOS
  • Examples
  • Forums
  • Docs
  • Install
  • Tutorials
  • COSMOS
  • Examples
  • Forums
  • Docs
Tutorials
>
COSMOS
>
Menu Icons

Chapter 18
Menu Icons

Tags
cosmos, animation

Estimated Time


Tutorials
>
COSMOS
>
Menu Icons

Next, we want to add the icons to our menu and animate them in. The design concept for the icons is straightforward: they start as a dot, move out to their final position, and animate to their full shape.

Positioning

The tricky part of this step isn’t the animation and movement – those are easy – but the positioning of each shape so that it looks like they are dots that turn into full shapes. There are a few ways of thinking about how to do this:

  1. create a dot, and a shape, hide the shape until the dot animates into position, hide the dot and reveal the shape
  2. create a dot made up of the same points as the shape, move the dot into position and morph the dot into shape
  3. have the “dot” not actually be a dot, but just look like one by setting the shape’s strokeEnd to the right position and its lineCap to .Round

We’re going to use the strokeEnd to manage the animating of the icons, so the third option is the best approach. However, the problem with this approach is that each of the “starting” points for the shapes will be different – we will need to offset them either on the dot or full icon end of the animation.

Here’s the icon for Capricorn, with its start point highlighted:

The position of the start point is {30.0,12.2}, which with respect to its frame is {0.750,0.387}.

To create the effect of a “dot” we simply set the following on each of the icons:

1
2
shape.strokeEnd = 0.001
shape.lineCap = .Round

If we use the center of the icon to position, the menu will look like this for its closed and open states.

If we use the first point as the anchorPoint of the shape we get the following:

Our ideal is using the anchorPoint for the closed state, and the center for the second state. But, this will get complicated if we are constantly switching positions. So, we’ll have to use the anchorPoint to calculate the actual center of the icon at one of the states and then use just the center to move it back and forth between those states afterwards.

Let’s get to it.

Grab the Icons

The first step is to grab the icons out of the sign provider, and update their anchor points.

Open MenuIcons.swift and add the following methods to the class:

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
func taurus() -> Shape {
    let shape = AstrologicalSignProvider.sharedInstance.taurus().shape
  shape.anchorPoint = Point()
  return shape
}

func aries() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.aries().shape
  shape.anchorPoint = Point(0.0777,0.536)
  return shape
}

func gemini() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.gemini().shape
  shape.anchorPoint = Point(0.996,0.0)
  return shape
}

func cancer() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.cancer().shape
  shape.anchorPoint = Point(0.0,0.275)
  return shape
}

func leo() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.leo().shape
  shape.anchorPoint = Point(0.379,0.636)
  return shape
}

func virgo() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.virgo().shape
  shape.anchorPoint = Point(0.750,0.387)
  return shape
}

func libra() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.libra().shape
  shape.anchorPoint = Point(1.00,0.559)
  return shape
}

func pisces() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.pisces().shape
  shape.anchorPoint = Point(0.099,0.004)
  return shape
}

func aquarius() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.aquarius().shape
  shape.anchorPoint = Point(0.0,0.263)
  return shape
}

func sagittarius() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.sagittarius().shape
  shape.anchorPoint = Point(1.0,0.349)
  return shape
}

func capricorn() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.capricorn().shape
  shape.anchorPoint = Point(0.288,0.663)
  return shape
}

func scorpio() -> Shape {
  let shape = AstrologicalSignProvider.sharedInstance.scorpio().shape
  shape.anchorPoint = Point(0.255,0.775)
  return shape
} 

Each of those methods grabs the raw icon from the sign provider and sets the anchor point to the start point of icon. We don’t need any of the other details from the sign (e.g. big / small points, lines, etc.) so we return only the shape with a modified anchor point.

Next, we also want to have a stored copy of the signs we’re working with because we’ll manipulate them a bit. So, we create a variable shape dictionary to store the signs and a method that will create and style them for us:

1
var signIcons : [String:Shape]!

Then, add the following method to your class:

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
func createSignIcons() {
    signIcons = [String:Shape]()
    signIcons["aries"] = aries()
    signIcons["taurus"] = taurus()
    signIcons["gemini"] = gemini()
    signIcons["cancer"] = cancer()
    signIcons["leo"] = leo()
    signIcons["virgo"] = virgo()
    signIcons["libra"] = libra()
    signIcons["scorpio"] = scorpio()
    signIcons["sagittarius"] = sagittarius()
    signIcons["capricorn"] = capricorn()
    signIcons["aquarius"] = aquarius()
    signIcons["pisces"] = pisces()
    
    for shape in [Shape](self.signIcons.values) {
        shape.strokeEnd = 0.001 //in combination with the next two settings
        shape.lineCap = .Round  //strokeEnd 0.001 makes a round dot at
        shape.lineJoin = .Round //the beginning of the shape's path
        
        shape.transform = Transform.makeScale(0.64, 0.64, 1.0)
        shape.lineWidth = 2
        shape.strokeColor = white
        shape.fillColor = clear
    }
}

The shape we’re getting out of the sign provider is raw, so we need to scale it to fit our design. If we don’t apply the following:

shape.transform = Transform.makeScale(0.64, 0.64, 1.0) Then the shapes would be too big and look like this:

Target Positions

Next, we need to calculate the target positions for each of the shapes and store them in two arrays. Add the following variables to your class:

1
2
var innerTargets : [Point]!
var outerTargets : [Point]!

Then, add the following method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func positionSignIcons() {
    innerTargets = [Point]()
    let provider = AstrologicalSignProvider.sharedInstance
    let r = 10.5
    let dx = canvas.center.x
    let dy = canvas.center.y
    for i in 0..<provider.order.count {
        let ϴ = M_PI/6 * Double(i)
        let name = provider.order[i]
        if let sign = signIcons[name] {
            sign.center = Point(r * cos(ϴ) + dx, r * sin(ϴ) + dy)
            canvas.add(sign)
            sign.anchorPoint = Point(0.5,0.5)
            innerTargets.append(sign.center)
        }
    }
    
    outerTargets = [Point]()
    for i in 0..<provider.order.count {
        let r = 129.0
        let ϴ = M_PI/6 * Double(i) + M_PI/12.0
        outerTargets.append(Point(r * cos(ϴ) + dx, r * sin(ϴ) + dy))
    }
}

The process above takes advantage of the following concepts:

  1. In creating the shapes we’ve already set the anchorPoint to each one’s start point
  2. Calling shape.center actually returns the position of the shape’s anchorPoint with respect to the shape’s superview
  3. Adjusting the anchorPoint back to the shapes center (e.g. {0.5,0.5}) won’t change the shape’s position
  4. After resetting the anchorPoint we are able to grab the actual center position of the shape and use this as a target

Finally, at the end of createSignIcons, after styling all the signs, add the following:

1
positionSignIcons()

Now, let’s have a look at how these line up.

Check It.

Change the backgroundColor of the menu to C4Purple and create the sign icons. Your setup() should look like this:

1
2
3
4
public override func setup() {
    canvas.backgroundColor = COSMOSbkgd
    createSignIcons()
}

And, go to your project’s WorkSpace and update the setup() there to look like:

1
2
3
override func setup() {
    canvas.add(MenuIcons().canvas)
}

Run it, and you should see this:

Animating the icons

There are 4 different animations we need to create: position out / in, shape reveal / hide. So, create 4 animation variables like so:

1
2
3
4
var signIconsOut : ViewAnimation!
var signIconsIn : ViewAnimation!
var revealSignIcons : ViewAnimation!
var hideSignIcons : ViewAnimation!

Then add this method:

1
2
func createSignIconAnimations() {
}

The reason we need 4 animations is based on our design, specifically the order in which we animate the lines out and in is different in either direction:

OUT: move, reveal IN: hide, move

Since the pattern is backwards when animating in we’re required to have separate animations for moving and for revealing that we can then order in a sequence. Add the following to the blank method:

1
2
3
4
5
6
7
8
9
10
11
12
13
revealSignIcons = ViewAnimation(duration: 0.5) {
    for sign in [Shape](self.signIcons.values) {
        sign.strokeEnd = 1.0
    }
}
revealSignIcons?.curve = .EaseOut

hideSignIcons = ViewAnimation(duration: 0.5) {
    for sign in [Shape](self.signIcons.values) {
        sign.strokeEnd = 0.001
    }
}
hideSignIcons?.curve = .EaseOut

A couple of things to note here:

  1. Having separate animations allows us to customize the easing for each step
  2. We can also specify the durations for either direction
  3. We set the stroke end back to 0.001 to preserve the dot

Now, add the following to the createSignIconAnimations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
signIconsOut = ViewAnimation(duration: 0.33) {
    for i in 0..<AstrologicalSignProvider.sharedInstance.order.count {
        let name = AstrologicalSignProvider.sharedInstance.order[i]
        if let sign = self.signIcons[name] {
            sign.center = self.outerTargets[i]
        }
    }
}
signIconsOut?.curve = .EaseOut

//moves icons to closed position
signIconsIn = ViewAnimation(duration: 0.33) {
    for i in 0..<AstrologicalSignProvider.sharedInstance.order.count {
        let name = AstrologicalSignProvider.sharedInstance.order[i]
        if let sign = self.signIcons[name] {
            sign.center = self.innerTargets[i]
        }
    }
}
signIconsIn?.curve = .EaseOut

Here all we’re doing is grabbing the icons and either setting their center to the outer or inner positions.

Check It.

Test the animations by calling the reveal and move animations like so:

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
func animOut() {
    delay(1.0) {
        self.signIconsOut?.animate()
    }

    delay(1.5) {
        self.revealSignIcons?.animate()
    }
    
    delay(2.5) {
        self.animIn()
    }
}

func animIn() {
    delay(0.25) {
        self.hideSignIcons?.animate()
    }

    delay(1.0) {
        self.signIconsIn?.animate()
    }

    delay(2.5) {
        self.animOut()
    }
}

Then call animOut() at the end of setup(), which should look like this:

1
2
3
4
5
6
public override func setup() {
    canvas.backgroundColor = COSMOSbkgd
    createSignIcons()
    createSignIconAnimations()
    animOut()
}

You should see this:

Clean Up

Now, delete animIn and animOut methods and make your setup() look like this:

1
2
3
4
5
6
public override func setup() {
    canvas.frame = Rect(0,0,80,80)
    canvas.backgroundColor = clear
    createSignIcons()
    createSignIconAnimations()
}

Grab a copy of MenuIcons.swift.

Baddabing!

Well done! Continue COSMOS?

  • Chapter 17
    Animating the Menu Rings

    Time to bring the radial menu to life with embedded animation.

  • Chapter 19
    Menu Selector

    Give the radial menu interaction and responsiveness with some programming wizardry.

  • About
  • Features
  • Learn + Explore
  • Install C4
  • Examples
  • Tutorials
  • Docs
  • Connect
  • Forums
  • Medium
  • Twitter
  • Slack
  • Vimeo
  • GitHub
  • © 2016 C4
  • Terms of Use
  • Contact
  • Learn + Explore
  • Install C4
  • Examples
  • Tutorials
  • Docs
  • Connect
  • Forums
  • Medium
  • Twitter
  • Slack
  • Vimeo
  • GitHub
  • © 2016 C4
  • Terms of Use
  • Contact