Silver, Bronze, and Chapter 6 in Swift


#1

Please let me know when you see anything that could be improved!
(also includes previous challenges)

Silver challenge is implemented with loadView() and controlCircleColor() in the HypnosisViewController.swift
Also, had to update touchesBegan() in HypnosisView.swift so that whenever a random color is chosen, the segmented controller is in its initial deselected state.
This got a little messy because I don’t know a good way to go about accessing a view’s view controller, anyone know how to go about that better?

AppDelegate.swift

[code]import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

// At the beginning of this implementation, HypnosisViewController is asked
// for its view. Since it has just been created, its "view" is nil, so it is
// send the "loadView" message that creates te "view" member
// The HypnosisViewController is being used to present the HypnosisView,
// instead of adding the view object itself to the window
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
    self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
    // Override point for customization after application launch.
    
    // Allows local Alert notifications to be used by this app
    let notificationSettings = UIUserNotificationSettings(forTypes:
        UIUserNotificationType.Alert, categories: nil)
    UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
    
    // Uses the convenience initializer to create an instance with
    // a dummy frame of all 0.0's. Doesn't matter since setting it as 
    // the root view controller will resize it.
    let hvc = HypnosisViewController()
    
    // Gets the object that represents the app bundle
    // The app buncdle is a directory on the filesystem that contains the 
    // apps executable and its resources (like NIB files) that the executable
    // will use
    //let appBundle = NSBundle.mainBundle()
    
    // Look in the appBundle for the file ReminderViewController.xib
    //let rvc = ReminderViewController(nibName: "ReminderViewController",
    //    bundle: appBundle)
    
    // Uses a convenience initializer that passes "nil" to nibName and bundle
    // When passed "nil", it looks in the main bundle for "ReminderViewController.xib"
    let rvc = ReminderViewController()
    
    // Same as for "rvc"
    let qvc = QuizViewController()
    
    // UITabBarController keeps an array of view controllers, and maintains
    // a tab bar at the bottom of the screen w/ a tab for each view controller
    // in the array. Tapping on a tab presents the view of the view controller
    // associated with that tab
    let tabBarController = UITabBarController()
    tabBarController.viewControllers = [hvc, rvc, qvc]
    
    // Adds the view controller's view hierarchy to the window
    // Setting a view controller as the rootViewController adds that view
    // controller's "view" as a subview of the window, and automatically
    // resizes the view to be the same size as the window
    //window!.rootViewController = hvc
    //window!.rootViewController = rvc
    window!.rootViewController = tabBarController
    
    window!.backgroundColor = UIColor.whiteColor()
    window!.makeKeyAndVisible()
    return true
}

}
[/code]

HypnosisViewController.swift

[code]//
// HypnosisViewController.swift
// Hypnonerd
//
// Created by adam on 6/16/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import UIKit

class HypnosisViewController: UIViewController {

override func loadView() {
    // Create a view
    // Uses convenience initializer to default size of mainScreen bounds
    let backgroundView = HypnosisView()
    
    // Set it as *the* view of this view controller
    view = backgroundView
    
    // Make and add a UISegmentedControl for controlling the circle color
    let segControl = UISegmentedControl(items: ["Red", "Green", "Blue"])
    segControl.frame = CGRectMake(35, 20, 250, 40)
    segControl.addTarget(self, action: "controlCircleColor:", forControlEvents: UIControlEvents.ValueChanged)
    view.addSubview(segControl)
}

func controlCircleColor(sender: AnyObject) {
    if let segControlSender = sender as? UISegmentedControl {
        let hypnosisView = view as HypnosisView
        switch segControlSender.selectedSegmentIndex {
        case 0:
            hypnosisView.setCircleColor(circleColor: UIColor.redColor())
        case 1:
            hypnosisView.setCircleColor(circleColor: UIColor.greenColor())
        case 2:
            hypnosisView.setCircleColor(circleColor: UIColor.blueColor())
        default:
            println("Error: SegControl selected index > 2")
        }
    }
}

// Shows if the view is utilizing lazy loading or called prematurely
// Override this if the configuration only needs to be done once during
// the run of the app
override func viewDidLoad() {
    // Always call the super implementation of viewDidLoad
    super.viewDidLoad()
    
    println("HypnosisViewController loaded its view")
}

init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    
    // Set the tab bar item's title
    tabBarItem.title = "Hypnotize"
    
    // Create a UIImage from a file
    // This will use Hypno@2x.png on retina display devices
    let i = UIImage(named: "Hypno.png")
    
    // Put that image on the tab bar item
    tabBarItem.image = i
}

convenience init() {
    self.init(nibName: nil, bundle: nil)
}

}
[/code]

HypnosisView.swift

[code]//
// HypnosisView.swift
// Hypnosister
//
// Created by adam on 6/13/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import UIKit

class HypnosisView: UIView {

var circleColor = UIColor.lightGrayColor()

func setCircleColor(circleColor color: UIColor) {
    circleColor = color
    setNeedsDisplay()
}

init(frame: CGRect) {
    super.init(frame: frame)
    // Initialization code
    
    // All HypnosisViews start with a clear background color
    // (a view's backgroundColor is drawn regardless of what drawRect does,
    // so often custom views are set to have a transparent background so that
    // only the results of drawRect show
    backgroundColor = UIColor.clearColor()
}

// Simply creates an instance for being automatically resized
convenience init() {
    self.init(frame: CGRectMake(0.0, 0.0, 0.0, 0.0))
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
    // First thing to do when overriding this: get the bounds
    let bounds = self.bounds
    
    // Figure out the center of the bounds rectangle
    let center = CGPoint(x: (bounds.origin.x + bounds.size.width / 2.0),
        y: (bounds.origin.y + bounds.size.height / 2.0))
    
    // The largest circle will circumscribe the view
    let maxRadius = CGFloat(hypot(Double(bounds.size.width), Double(bounds.size.height)) / 2.0)

    // Draw the circle using UIBezierPath; draws lines and curves to make shapes
    let path = UIBezierPath()
    
    // Could have multiple instances of UIBezierPath, each one being one circle, 
    // or add multiple circles to a single instance. slightly more effcient w/ single instance
    for var currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20 {
        
        // "pick up" the "pencil" and move it to the correct starting spot
        path.moveToPoint(CGPointMake(center.x + currentRadius, center.y))
        
        path.addArcWithCenter(center, radius: currentRadius, startAngle: 0,
            endAngle: CGFloat(M_PI * 2.0), clockwise: true)

        //println("drawing circle... x:\(center.x)   y:\(center.y)   radius:\(currentRadius)")
    }
    
    // Make the line width 10 points
    path.lineWidth = 10
    
    // Sets the color of subsequent stroke operations to the color that the receiver represents
    circleColor.setStroke()
    
    // Draw the line!
    path.stroke()
    
    // Shadows cannot be "unset", so the current context must be saved and
    // restored from after the shawdow is used
    var baseContext = UIGraphicsGetCurrentContext()
    CGContextSaveGState(baseContext)
    
    // Calculate values to use for drawing the logo
    let logoRectWidth = bounds.size.width / 2.0
    let logoRectHeight = bounds.size.height / 2.0
    let logoRectOriginX = logoRectWidth / 2.0
    let logoRectOriginY = logoRectHeight / 2.0
    
    // Draw a triangle path using the logo calculated values
    let triangleTop = CGPointMake(logoRectWidth, logoRectOriginY - 20.0)
    let triangleBottomLeft = CGPointMake(logoRectOriginX - 10.0,
        logoRectOriginY + logoRectHeight + 20.0)
    let triangleBottomRight = CGPointMake(logoRectOriginX + logoRectWidth + 10.0,
        logoRectOriginY + logoRectHeight + 20.0)
    let triangleBottomMiddle = CGPointMake(logoRectWidth,
        logoRectOriginY + logoRectHeight + 20.0)
    
    // Set "pencil" down at the top of the triangle, then draw the edges
    let trianglePath = UIBezierPath()
    trianglePath.moveToPoint(triangleTop)
    trianglePath.addLineToPoint(triangleBottomLeft)
    trianglePath.addLineToPoint(triangleBottomRight)
    trianglePath.addLineToPoint(triangleTop)
    //trianglePath.stroke()
    
    // Use the triangle path to draw a gradient
    // Gradients cover everything in the view, so a clipping path must be 
    // installed on the graphics context that defines what the gradient covers
    trianglePath.addClip()
    
    // Create the gradient
    let locations: CGFloat[] = [0.0, 1.0]
    let components: CGFloat[] = [ 0.0, 1.0, 0.0, 1.0, // Start color: green
                                    1.0, 1.0, 0.0, 1.0] // End color: yellow
    var colorspace = CGColorSpaceCreateDeviceRGB()
    var gradient = CGGradientCreateWithColorComponents(colorspace, components,
        locations, 2)
    
    // Draw the gradient
    CGContextDrawLinearGradient(baseContext, gradient, triangleTop,
        triangleBottomMiddle, 0)
    
    // Deallocate (note: make sure gradient and colorspace are defined with
    // "var" instead of "let", or else this will give an error
    //CGGradientRelease(gradient)
    //CGColorSpaceRelease(colorspace)
    
    // Restore the graphics state to clear the clip path
    CGContextRestoreGState(baseContext)

    baseContext = UIGraphicsGetCurrentContext()
    CGContextSaveGState(baseContext)
    // Now anything drawn will appear with a shadow
    CGContextSetShadow(baseContext, CGSizeMake(4, 7), 3)
    
    // Create UIImage object from logo.png imported file, draw it over circles
    let logoImage = UIImage(named: "logo.png")
    
    logoImage.drawInRect(CGRect(x: logoRectOriginX, y: logoRectOriginY,
        width: logoRectWidth, height: logoRectHeight))
    
    // Now anything drawn won't have a shadow
    CGContextRestoreGState(baseContext)
}


// When a finger touches the view; this is a touch event handler
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
    println("\(self) was touched")
    
    // If the segmented controller is set, deselect all segments
    let rvc = UIApplication.sharedApplication().delegate.window!.rootViewController
    if let tvc = rvc as? UITabBarController {
        if let hvc = tvc.viewControllers[0] as? HypnosisViewController {
            for view : AnyObject in hvc.view.subviews {
                if view is UISegmentedControl {
                    let segControl = view as UISegmentedControl
                    if segControl.selectedSegmentIndex == 0 ||
                       segControl.selectedSegmentIndex == 1 ||
                       segControl.selectedSegmentIndex == 2
                    {
                        println("Reset all segments")
                        segControl.selectedSegmentIndex = UISegmentedControlNoSegment
                    }
                }
            }
        }
    }

    // Get 3 random numbers between 0 and 1
    let red = CGFloat(arc4random() % 100) / 100.0
    let green = CGFloat(arc4random() % 100) / 100.0
    let blue = CGFloat(arc4random() % 100) / 100.0
    
    let randomColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0)
    
    setCircleColor(circleColor: randomColor)
}

}
[/code]

ReminderViewController.swift

[code]//
// ReminderViewController.swift
// Hypnonerd
//
// Created by adam on 6/16/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import UIKit

class ReminderViewController: UIViewController {

@IBOutlet var datePicker: UIDatePicker

@IBAction func addReminder(AnyObject) {
    var date = datePicker.date
    println("Setting a reminder for \(date)")
    
    let note = UILocalNotification()
    note.alertBody = "Hypnotise me!"
    note.fireDate = date
    
    // To be able to use this, notifications had to be specified
    // with UIUserNotificationSettings, then registered
    // (see top of didFinishLaunchingWithOptions function code in AppDelegate.swift)
    UIApplication.sharedApplication().scheduleLocalNotification(note)
}

// Shows if the view is utilizing lazy loading or called prematurely
// Override this if the configuration only needs to be done once during
// the run of the app
override func viewDidLoad() {
    // Always call the super implementation of viewDidLoad
    super.viewDidLoad()
    
    println("ReminderViewController loaded its view")
}

// Override this if the configuration needs to be done and redone every time
// the view controller appears on screen
override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    println("ReminderViewController did appear")
    
    datePicker.minimumDate = NSDate(timeIntervalSinceNow: 60)
}

init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
    super.init(nibName[code][/code]: nibNameOrNil, bundle: nibBundleOrNil)
    
    // Give it a label
    tabBarItem.title = "Reminder"
    
    tabBarItem.image = UIImage(named: "Time.png")
}

convenience init() {
    self.init(nibName: nil, bundle: nil)
}

}
[/code]

QuizViewController.swift

[code]//
// QuizViewController.swift
// Quiz
//
// Created by adam on 6/11/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import UIKit

class QuizViewController: UIViewController {

// Model Objects
var currentQuestionIndex: Int

var questions: String[]
var answers: String[]

// View Objects
/*
When you declare an outlet in Swift, the compiler automatically converts the 
type to a weak implicitly unwrapped optional and assigns it an initial value 
of nil. In effect, the compiler replaces @IBOutlet var name: Type with
@IBOutlet weak var name: Type! = nil. The compiler converts the type to an 
implicitly unwrapped optional so that you aren’t required to assign a value 
in an initializer.
*/

// IBOutlet keyword tells Xcode that this outlet will be set usuing 
// Interface Builder

// For outlets: you drag FROM the object w/ the outlet that you want to set
// TO the object that you want that outlet to point to
@IBOutlet var questionLabel: UILabel

@IBOutlet var answerLabel: UILabel


// Intialization
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
{
    // create two arrays filled with questions and answers
    currentQuestionIndex = 0
    
    questions = ["From what is cognac made?", "What is 7+7?",
        "What is the capital of Vermont?"]
    answers = ["Grapes", "14", "Montpelier"]
    
    // call the init method implemented by the superclass
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    
    tabBarItem.title = "Quiz"
    //tabBarItem.image = UIImage(named: "Icon.png")
}
// The app will search for "QuizViewController.xib" in the mainBundle
convenience init() {
    self.init(nibName: nil, bundle: nil)
}


// Action Methods

// IBAction tells Xcode that you will be making the target and action
// connections in Interface Builder

// Because the sender parameter of the buttonTapped: method wasn’t used, 
// the parameter name can be omitted.

// For targets: you control-drag FROM the object TO its target, then
// select the action
@IBAction func showQuestion(AnyObject) {
    // Step to the next question
    ++currentQuestionIndex
    
    // Am I past the last question?
    if (currentQuestionIndex == questions.count) {
        
        // Go back to the first question
        currentQuestionIndex = 0
    }
    
    // Get the string at that index in the questions array
    var question = questions[currentQuestionIndex]
    
    // Display the string in the question label
    questionLabel.text = question
    
    // Reset the answer label
    answerLabel.text = "???"
}

@IBAction func showAnswer(AnyObject) {
    // What is the answer to the current question?
    var answer = answers[currentQuestionIndex]
    
    // Display it in the answer label
    answerLabel.text = answer
}



// Shows if the view is utilizing lazy loading or called prematurely
// Override this if the configuration only needs to be done once during
// the run of the app
override func viewDidLoad() {
    // Always call the super implementation of viewDidLoad
    super.viewDidLoad()
    
    println("QuizViewController loaded its view")
}

}
[/code]


#2

In HypnosisView.swift try

if let segControl = self.subviews[0] as? UISegmentedControl { segControl.selectedSegmentIndex = UISegmentedControlNoSegment }