[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.
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. } }
Leave a Reply