Instantly share code, notes, and snippets.
C4Framework/Menu.swift
Last active Mar 16, 2016
Chapter 7.6 of the Cosmos tutorial
// Copyright © 2015 | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to | |
// deal in the Software without restriction, including without limitation the | |
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
// sell copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: The above copyright | |
// notice and this permission notice shall be included in all copies or | |
// substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
// IN THE SOFTWARE. | |
import UIKit | |
typealias SelectionAction = (selection: Int) -> Void | |
typealias InfoAction = () -> Void | |
class Menu : CanvasController { | |
//MARK: - | |
//MARK: Properties | |
//A canvas that handles drawing and animating the menu's rings | |
var menuRings : MenuRings! | |
//A canvas that handles drawing and animating the menu's icons | |
var menuIcons : MenuIcons! | |
//A canvas for handling the gesture and selection highlight | |
var menuSelector : MenuSelector! | |
//A canvas for animating the shadow of the menu | |
var menuShadow : MenuShadow! | |
//A boolean for tracking when the menu is expanded (or expanding) | |
var menuIsVisible = false | |
//A boolean to track if the menu should revert to its closed state | |
var shouldRevert = false | |
//A label that instructs the user to press and hold on the menu | |
var instructionLabel : UILabel! | |
//A timer that will reveal the menu, shortly after the app opens and if the user doesn't press the menu | |
var timer : Timer! | |
//An action to trigger when the user has selected one of the icons from the menu | |
var selectionAction : SelectionAction? | |
//An action to trigger when the user has chosen the info button from the menu | |
var infoAction : InfoAction? | |
//A sound to play when the menu closes | |
let hideMenuSound = AudioPlayer("menuClose.mp3")! | |
//A sound to play when the menu opens | |
let revealMenuSound = AudioPlayer("menuOpen.mp3")! | |
//MARK: - | |
//MARK: Setup | |
override func setup() { | |
//clear the background | |
canvas.backgroundColor = clear | |
//make the canvas frame fairly small | |
canvas.frame = Rect(0,0,80,80) | |
//create the rings | |
menuRings = MenuRings() | |
//create the selector | |
menuSelector = MenuSelector() | |
//create the icons | |
menuIcons = MenuIcons() | |
//create the shadow | |
menuShadow = MenuShadow() | |
menuShadow.canvas.center = canvas.bounds.center | |
//add the canvases of each object in specific order (back to front) | |
canvas.add(menuShadow.canvas) | |
canvas.add(menuRings.canvas) | |
canvas.add(menuSelector.canvas) | |
canvas.add(menuIcons.canvas) | |
//create a gesture to handle interaction | |
createGesture() | |
//create the instruction label | |
createInstructionLabel() | |
//create and start the timer, it will execute one time | |
timer = Timer(interval: 5.0, count: 1) { | |
self.showInstruction() | |
} | |
timer?.start() | |
//makes the sounds a little quieter | |
hideMenuSound.volume = 0.64 | |
revealMenuSound.volume = 0.64 | |
} | |
//MARK: - | |
//MARK: Gesture | |
func createGesture() { | |
//add a long press gesture to the menu's canvas | |
canvas.addLongPressGestureRecognizer { (locations, center, state) -> () in | |
switch state { | |
case .Began: | |
self.revealMenu() //reveals the menu when the gesture begins | |
case .Changed: | |
self.menuSelector.update(center) //updates the menu based on the user's press location | |
case .Cancelled, .Ended, .Failed: | |
//if the selection action exists | |
if let sa = self.selectionAction where self.menuSelector.currentSelection >= 0 { | |
//trigger it with the current selection | |
sa(selection: self.menuSelector.currentSelection) | |
} | |
//hide the menu label | |
self.menuSelector.menuLabel?.hidden = true | |
//reset the current selection | |
self.menuSelector.currentSelection = -1 | |
//disable interaction (temporarily) | |
self.canvas.interactionEnabled = false | |
//hide the highlight if it is visible | |
if self.menuSelector.highlight?.hidden == false { | |
self.menuSelector.highlight?.hidden = true | |
} | |
//hide the menu if it is visible | |
if self.menuIsVisible { | |
self.hideMenu() | |
} else { | |
//if the menu isn't visible (i.e. still animating out), flag it to revert | |
self.shouldRevert = true | |
} | |
//if the infobutton exists | |
if let ib = self.menuSelector.infoButton { | |
//hit test it to see if the user has selected info | |
if ib.hitTest(center, from: self.canvas), | |
//if so, wait a bit (for the menu to close) and then run the info action | |
let ia = self.infoAction { | |
wait(0.75) { | |
ia() | |
} | |
} | |
} | |
default: | |
_ = "" | |
} | |
} | |
} | |
//MARK: - | |
//MARK: Menu Reveal / Hide | |
func revealMenu() { | |
//stop the timer | |
timer?.stop() | |
//hide the instruction label | |
hideInstruction() | |
//flag the menu as not visible (just in case) | |
menuIsVisible = false | |
//play the reveal sound | |
revealMenuSound.play() | |
//reveal the shadow | |
menuShadow.reveal?.animate() | |
//animate the thick ring | |
menuRings.thickRingOut?.animate() | |
//animate the thin rings | |
menuRings.thinRingsOut?.animate() | |
//move the icons to their outer positions | |
menuIcons.signIconsOut?.animate() | |
//wait a bit | |
wait(0.33) { | |
//reveal the dividing lines | |
self.menuRings.revealHideDividingLines(1.0) | |
//reveal the sign icons | |
self.menuIcons.revealSignIcons?.animate() | |
} | |
//wait a little bit more | |
wait(0.66) { | |
//reveal the dashed rings | |
self.menuRings.revealDashedRings?.animate() | |
//reveal the info button | |
self.menuSelector.revealInfoButton?.animate() | |
} | |
//the menu should be open at this point | |
wait(1.0) { | |
//so, set visible to true | |
self.menuIsVisible = true | |
//check if the menu should revert (i.e. if the user released the press while the menu was animating out | |
if self.shouldRevert { | |
self.hideMenu() | |
self.shouldRevert = false | |
} | |
} | |
} | |
func hideMenu() { | |
//if the instruction label is visible, hide it | |
if instructionLabel?.alpha > 0.0 { | |
hideInstruction() | |
} | |
//treat the menu as not visible while animating back to its closed state | |
menuIsVisible = false | |
//play the hide sound | |
hideMenuSound.play() | |
//hide the dashed rings | |
menuRings.hideDashedRings?.animate() | |
//hide the info button | |
menuSelector.hideInfoButton?.animate() | |
//hide the dashed lines | |
menuRings.revealHideDividingLines(0.0) | |
//wait a little bit | |
wait(0.16) { | |
//hide the icons | |
self.menuIcons.hideSignIcons?.animate() | |
} | |
//wait a little bit longer | |
wait(0.57) { | |
//animate the thin rings | |
self.menuRings.thinRingsIn?.animate() | |
} | |
//wait just a tiny bit more | |
wait(0.66) { | |
//move the menu icons back to their close position | |
self.menuIcons.signIconsIn?.animate() | |
//animate the thick ring in | |
self.menuRings.thickRingIn?.animate() | |
//hide the shadow | |
self.menuShadow.hide?.animate() | |
//re-enable interaction on the canvas | |
self.canvas.interactionEnabled = true | |
} | |
} | |
//MARK: - | |
//MARK: Instruction Label | |
func createInstructionLabel() { | |
//create a basic label, style it and add it to the canvas | |
instructionLabel = UILabel(frame: CGRect(x: 0,y: 0,width: 320, height: 44)) | |
instructionLabel.text = "press and hold to open menu\nthen drag to choose a sign" | |
instructionLabel.font = UIFont(name: "Menlo-Regular", size: 13) | |
instructionLabel.textAlignment = .Center | |
instructionLabel.textColor = .whiteColor() | |
instructionLabel.userInteractionEnabled = false | |
instructionLabel.center = CGPointMake(view.center.x,view.center.y - 128) | |
instructionLabel.numberOfLines = 2 | |
instructionLabel.alpha = 0.0 | |
canvas.add(instructionLabel) | |
} | |
func showInstruction() { | |
ViewAnimation(duration: 2.5) { | |
self.instructionLabel?.alpha = 1.0 | |
}.animate() | |
} | |
func hideInstruction() { | |
ViewAnimation(duration: 0.25) { | |
self.instructionLabel?.alpha = 0.0 | |
}.animate() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment