20 Cocoa Apps Challenge

Cocoa

App 03: Pick a Color v1

  • Assets, NSImageView, NSImage
  • Optionals, Forced and Implicit Unwrapping
  • NSPopUpButton: Getting user's selection

Controls

Outlets and Actions

This means that NSPopUpButton can display values (e.g., the color of the shirts) and can act when its values are selected.

NSImageView can display images one at a time. Since it is not connected as an action, it cannot act when the user, for example, clicks or double-clicks on the image.

Image View

An NSImageView object displays an image. It acts like a frame for an image, and works hand-in-hand with NSImage

Assets.xcassets

Before an NSImage object can be used by NSImageView, it must exist inside Assets.xcassets folder.

Optionals and Forced Unwrapping

To be or not to be, that is the question

An optional represents two possibilities: Is there a value or is there none. For example, a lot of people have second names, like Donald John Trump, or William Jefferson Clinton, but an equal number of people do not. So what to do?

Enter optionals. Optionals are used to cope with the possibility of a variable or a constant having no value at all. To indicate this, a question mark is appended to the end of the type:
let secondName: String?

In this state, secondName has the value nil.

In a dynamically typed language, like Ruby, this is no big deal. But in a strongly typed language like Swift, this is a very big deal. Why? Since nil is not a String type, Swift will not allow the nil value to be assigned to secondName. The closest value it can allow is an empty string value "". But an empty string doesn't express the absence of value (i.e., a string value still exist albeit an empty one), only nil can.

secondName = "Jefferson"

If we assign secondName a value, like in the above, it will not have the String type "Jefferson" value that you expect but rather an Optional("Jefferson")!

Assigning a value to an optional variable/constant makes the value to have been wrapped. "Jefferson" is literally wrapped inside an Optional(). So in order to extract Optional("Jefferson"), it will need to be unwrapped.

To unwrap forcefully or to unwrap implicitly, that is the question

For now, we use forced unwrapping to unwrap secondName, and it goes something like this:

let billsSecondName = secondName!

The ! is the syntax for forced unwrapping. billsSecondName will now contain "Jefferson" since Optional("Jefferson") has been forced unwrapped from secondName.

Again, the main reason we have to go through this song and dance is because of the possibility that a constant or variable will not contain any value at all, and we should be able to handle it when it happens.

Implicit Unwrapping

Now, there will be situations where we can be absolutely sure that a variable or a constant will contain a value. A good example is our NSPopUpButton and NSImageView controls.

We can be absolutely sure that the color and shirt variables do contain a value, respectively. How? It's because we created those UI controls in Xcode and connected them as outlets ourselves. We know they are there. Xcode guarantees that they are there.

In the illustration below, we declare color to be an implicitly unwrapped NSPopUpButton variable, and shirt as an implicitly unwrapped NSImageView variable. Here we're telling the Swift compiler to trust us, that both color and shirt will be assigned their respective UI control objects.

This is why inside the onColorSelect action, we can just access (get or set) color's and shirt's properties with the full confidence that both variables are not nil.

NSPopUpButton

Getting the user's selection indexOfSelectedItem and titleOfSelectedItem

How To

Code

//
//  AppDelegate.swift
//  Pick a Color v1
//  Created by Arthur Kho (github.com/islandjoe) on 08/16.
//
//  MIT License
//  Copyright (c) 2016 Arthur Kho
//

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

  @IBOutlet weak var window: NSWindow!
  @IBOutlet weak var color: NSPopUpButton!
  @IBOutlet weak var shirt: NSImageView!

  @IBAction func onColorSelect(sender: NSPopUpButton) {

    if color.indexOfSelectedItem == 0 {

      shirt.image = NSImage(named: "Neutral")

    }
    else {

      let shirtColor = color.titleOfSelectedItem!
      shirt.image    = NSImage(named: shirtColor)

    }

  }
}

20 Cocoa Apps Challenge