The Swift Swift Tutorial: Using Dictionaries and Optional Types

[Updated for Swift 2.0/iOS9 9/10/2015 – SJL ]
In our last lesson, The PizzaDemo App so far told us some information about pizza sizes. What would be nice is for it to tell us the price for a pizza, based on the area of the pizza. We can learn about Swift dictionaries that way.

For this lesson, you can either add the code to your existing project you can find here. If you are only interested in dictionaries and optionals without connecting it to the user interface we created in the earlier lesson, you can open a new playground called SwiftDictionariesOptionals.  Add the following code to the playground:

//Playground version of Pizza
class Pizza{  
    let pi = 3.14
    var pizzaType = "Cheese"
    var  z = 0.0
    var diameter:Double = 0.0{
        didSet{
            z = diameter/2.0
        }
    }
    func pizzaArea() -> Double{
        return pi * z * z // = a
    }
}

This makes a function pizzaArea that calcualtes the area of a circle from the property z, which is the radius. For some, the declaration of diameter may seem strange. If you are not familiar with computed properties, when we set a diameter, we’ll automatically divide it by two to get a radius and store that value in z. I discusss this more in my lessons on classes, which you can find here. This is all we need for this lesson. When we get to testing I’ll give directions on how to test using the playground.

We’ll put the price per square inch of each type of pizza in a dictionary.  To our current pizza app, add this line to the Pizza class, just under the class declaration  for Pizza

let pizzaPricePerInSq = [
    "Cheese": 0.03 ,
    "Sausage": 0.06 ,
    "Pepperoni": 0.05 ,
    "Veggie": 0.04
]

This creates a dictionary. If you are used to Objective-C’s version:

NSDictionary *pizza = @{
    @"Cheese" : [NSNumber numberWithDouble:13],
    @"Sausage" : [NSNumber numberWithDouble:22],
    @"Pepperoni" : [NSNumber numberWithDouble:19],
    @"Veggie" : [NSNumber numberWithDouble:16],
};

Swift’s dictionaries is a welcome relief. Dictionary entries use the key:value syntax, just like Objective-C, but with a lot less baggage. All keys must be the same type, and must be hashable, which includes all the basic type of Int, String, Double, and Bool. As long as you do not set a value to an enumeration, enumerations can also be used as a key. Types, like everywhere in Swift are implicitly declared.

We use the let declaration in this case. Using let makes this a immutable dictionary. Neither the number of elements or the values of the elements change. Making a mutable dictionary is simple: use var instead of let.

Let’s compute the price of a pizza. Add the following to the Pizza class:

    func pizzaPrice() -> Double{
        return pizzaArea() * pizzaPricePerInSq[pizzaType]
    }

This will give an error
Value of optional type 'Double?' not unwrapped
The error helpfully gives a fix to add a ! to your code. Any type with a question mark on the end of the type is an optional value. Optional values, besides having a value, has a nil state. Accessing a dictionary returns an optional value, returning nil if there is no key found. These values however are not directly usable. Optional values must be unwrapped to become a value with the ! operator.  The ! operator only works when the optional vale is non-nil.  If the value is nil, it will cause a run-time error.  In our case, using ! is probably safe, but it would be better to replace the pizzaPrice() method with the following code:

 func pizzaPrice() ->Double{
        let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
        if (unitPrice != nil){                                   //optional type ?Double checking for nil
            return pizzaArea() * unitPrice!             //unwrapping the optional type
        }
        return 0.0
    }

Line 2 finds the Double? value in the dictionary. We then check if the value is nil. If not, we multiply the area by the unwrapped unitPrice. If nil we return a value of 0.0.

Display And testing

We are ready to test this function. depending if you used the playground or set this up in the pizza app, the next step is different.

In the Pizza App

If you are following along and added this code to the PizzaDemo app, change the function in the view controller to:

func displayPizza(){
        let displayString = String(format:"%6.1f %@ $%6.2fUS",pizza.pizzaDiameter, pizza.pizzaType,pizza.pizzaPrice())
        resultsDisplayLabel.text = displayString
    }

Build and run.

Screenshot 2014-06-27 14.19.25

Playground

If you are using the playground, after the class type the following:

let pizza = Pizza()
pizza.diameter = 10
pizza.pizzaType = "Cheese"
print (pizza.pizzaArea())
print(pizza.pizzaPrice())

You get a pizza area of 7.85 and price of 2.855. Chnge the pizza type from Cheese to Sausage:

pizza.pizzaType = "Sausage"

the price changes to 4.71.

Now we have a price for every pizza, based on size and ingredients. This was a simple and bit contrived example, but it shows how easy using dictionaries are using Swift. For more on Dictionaries, check out my postHow to use Dictionaries in Swift.

Optional chaining

The code for pizzaPrice is written in its long form. There is a much shorter form. Comment out the current pizzaPrice method and change it to this:

 func pizzaPrice() -> Double{
        if let unitPrice = pizzaPricePerInSq[pizzaType] {//Optional chaining
            return pizzaArea() * unitPrice
        }
        return 0.0
    }

When we put the assignment of unitPriceas our condition of the if statement we use Optional Chaining. Optional chaining does three things: It unwarps our optional vaule, assigns it to a constant, and returns a value of false if the optional is nil. So with the one let statement embedded in the if statement, we remove a lot fo lines, and don’t have to unwrap unitPrice. You will see this often in code written by others. I discuss optional chaining a lot more in my more detailed post on optionals.

Our next lesson

I made up the values in the dictionary, but what if we were to calculate those numbers on another view? We would need segues and delegates to transmit the information back and forth between views. That will be our next exploration.

The Whole Code

The Playground: SwiftDictionariesOptionals.playground

//: Playground - noun: a place where people can play
// SwiftDictionariesOptionals.playground
// (c)2015 Steven Lipton 

import UIKit
class Pizza{
   let pizzaPricePerInSq = [
        "Cheese": 0.03 ,
        "Sausage": 0.06 ,
        "Pepperoni": 0.05 ,
        "Veggie": 0.04
    ]
    let pi = 3.14
    var pizzaType = "Cheese"
    var  z = 0.0
    var diameter:Double = 0.0{
        didSet{
            z = diameter/2.0
        }
    }
    
    func pizzaArea() -> Double{
        return pi * z * z // = a
    }
   /*
    func pizzaPrice() ->Double{
        let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
        if (unitPrice != nil){                                   //optional type ?Double checking for nil
            return pizzaArea() * unitPrice!             //unwrapping the optional type
        }
        return 0.0
    }
*/
    func pizzaPrice() -> Double{
        if let unitPrice = pizzaPricePerInSq[pizzaType] {//Optional chaining
            return pizzaArea() * unitPrice
        }
        return 0.0
    }
}

let pizza = Pizza()
pizza.diameter = 10.0
//pizza.pizzaType = "Cheese"
pizza.pizzaType = "Sausage"
print (pizza.pizzaArea())
print(pizza.pizzaPrice())

PizzaDemo:ViewController.swift

This is the changes to the model and view controller from the PizzaDemo app

//
//  ViewController.swift
//  PizzaDemo2
//
//  Created by Steven Lipton on 9/1/15.
//  Copyright © 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class Pizza
{
    let pizzaPricePerInSq = [
        "Cheese": 0.03 ,
        "Sausage": 0.06 ,
        "Pepperoni": 0.05 ,
        "Veggie": 0.04
    ]
    let pi = 3.1415926
    let maxPizza = 24.0
    
    var pizzaDiameter = 0.0
    var pizzaType = "Cheese"
    var radius : Double {  //computed property
        get{   //must define a getter
            return pizzaDiameter/2.0
        }
        set(newRadius){ //optionally define a setter
            pizzaDiameter = newRadius * 2.0
        }
    }
    
    var area :  Double {
        get{
            return pizzaArea()
        }
    }
    
    
    func pizzaArea() -> Double{
        return radius * radius * pi
    }
    
    func diameterFromString(aString:String) -> Double {
        switch aString {
        case "Personal":
            return 8.0
        case "10\"":
            return 10.0
        case "12\"":
            return 12.0
        case "16\"","15\"":
            return 16.0
        case "18\"":
            return 18.0
        case "24\"":
            return 24.0
        default:
            return 0.0
        }
    }
    
    /*
    func pizzaPrice() ->Double{
    let unitPrice = pizzaPricePerInSq[pizzaType]    //optional type ?Double
    if (unitPrice != nil){                                   //optional type ?Double checking for nil
    return pizzaArea() * unitPrice!             //unwrapping the optional type
    }
    return 0.0
    }
    */
    func pizzaPrice() -> Double{
        if let unitPrice = pizzaPricePerInSq[pizzaType] {//Optional chaining
            return pizzaArea() * unitPrice
        }
        return 0.0
    }
    
    
}

class ViewController: UIViewController {
    
    let pizza = Pizza()
    let clearString = "I Like Pizza!"
    
    @IBOutlet var resultsDisplayLabel : UILabel!
    @IBAction func pizzaType(sender : UISegmentedControl) {
        let index = sender.selectedSegmentIndex
        pizza.pizzaType = sender.titleForSegmentAtIndex(index)!
        displayPizza()
    }
    @IBAction func sizeButton(sender : UIButton) { //blue button
        pizza.pizzaDiameter = pizza.diameterFromString(sender.titleLabel!.text!)
        displayPizza()
    }
    @IBAction func clearDisplayButton(sender : UIButton) { //red button
        resultsDisplayLabel.text = clearString
    }
    
    func displayPizza(){
        let displayString = String(format:"%6.1f %@ $%6.2fUS",pizza.pizzaDiameter, pizza.pizzaType,pizza.pizzaPrice())
        resultsDisplayLabel.text = displayString
    }
    
    

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        resultsDisplayLabel.text = clearString
        view.backgroundColor = UIColor(red:0.99,green:0.9,blue:0.9,alpha:1.0)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}


4 Replies to “The Swift Swift Tutorial: Using Dictionaries and Optional Types”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s