Make App Pie

Training for Developers and Artists

How to Use Dictionaries in Swift 3.0

What is a Dictionary

An ordered, indexed array and a unordered dictionary with keys
An ordered, indexed array and a unordered dictionary with keys

Dictionaries and arrays make up the collection types of Swift. Collections are exactly what they say: a collection of values. The difference between an array and a dictionary is how you get the values in and out of the collection. Arrays, as we covered elsewhere, are ordered collections. They have an index which gives their exact place in the collection. I know with an array a value by its position in the array. I know what is before and after my current position by changing the index from its current position.  Arrays must use positive integers in sequence to work this magic.

Dictionaries are unordered collections. No one knows which value comes next, and we really don’t care. The power of a dictionary is we have a key, and the key can pretty much be anything we like that’s a unique value.  One of the most common keys will be strings, but integers and doubles can certainly be used. Dictionaries look up values by the key.

While in an array we pair an index with a value, we pair a key with a value in a dictionary. In Swift, all keys are the same type and all values are the same type in a given dictionary. The value can be of any type, even other objects or collection types. We call the key, then a value returns. Often we find a syntax of key:value for dictionary pairs.

In this lesson for most of these examples, we’ll use a playground. Set up a new playground named SwiftDictionary.

How to Declare a Dictionary

The simplest declaration is to let implicit typing do all the work. We declare a variable or constant and list the key:value pairs in square brackets.

let cookies = ["Chocolate Chip":0.25,"Oatmeal":0.26]

This would create a unmutable dictionary. We can refer to it, but can not change any value. For a mutable dictionary, we could use this:

var mutableCookies = [
    "Macadamia Nut":0.06,
    "Coconut":0.20,
    "Macaron":0.55
]

Dictionaries can have entries on one line or multiple lines. To help with readability in a long list of values, you can list them on multiple lines like this:

var gelato = [
      "Coconut":0.25,
       "Pistachio":0.26,
       "Stracciatella":0.02,
      "Chocolate":0.03,
      "Peanut Butter":0.01,
      "Bubble Gum":0.01
]

You can explicitly type your of key:value pairs. The preferred way to do this is:

let piePrice:[String:Double] = [
   "Apple":3.99,
   "Raspberry":3.35
]

We specify the types in the key value pair surrounded by square brackets. Here we have a string for the key, and a double for the value. There is another declaration which is available but frowned on by Apple.

let piPrice:Dictionary<String,Double> = [
    "Apple Mac":1200.00,
    "Raspberry Pi":45.00
]

This code is identical in function to the previous example but writes out the word Dictionary and uses the <and > characters around the key value pair types.

There are times you need an empty dictionary. In those cases, call the dictionary initializer, which for a dictionary is the key:value pair enclosed in square brackets:

var pieToppings = [String:Double]()

Alternatively, though frowned upon by Apple, you can do this too:

var pizzaSizes = Dictionary<Int, String>()

How to Access a Dictionary

If I wanted to know the price of two slices of pie, the basic syntax is the same for an array, but instead of an index in the square brackets we place the key. We’ve already declared pie prices per slice. How much is an apple pie slice? Like an array’s index, you place the key on brackets:

print(piePrice["Apple"])

How much for a gram of coconut gelato?

print(gelato["Coconut"])

The code piePrice["Apple"] prints the price of a slice of apple pie and the gelato returns the price per gram.

Optional(3.99)
Optional(0.25)

You’ll notice the values returned are optionals. Returned dictionary values are always optionals. At any given time, I do not know if there is a certain type of pie or not in my dictionary. If I tried piePrice["Coconut Cream"] I need some way to tell me that there is no coconut cream pies in my dictionary. Type

print(piePrice["Coconut Cream"])

The playground print nil to the console. Swift makes the return value of a dictionary an optional value, so it will return nil if there is no such entry.

If your dictionary is a constant, you know exactly what’s there. In that case, we can use a forced unwrap to calculate the price for 100 grams of Coconut gelato:

print(gelato["Coconut"]! * 100.0)

If you have a dictionary in a variable, where you may add or delete elements from the dictionary, make sure you check for nil. For two slices of apple pie, we could write this:

let slices = 2.0
if piePrice["Apple"] != nil {
    let extPrice = piePrice["Apple"]! * slices
    print("\(slices) slices of Apple Pie is \(extPrice)")
}

or more compactly with optional chaining

if let price = piePrice["Apple"]{
    let extPrice = price * slices
    print("\(slices) slices of Apple Pie is \(extPrice)")
}

If you are not familiar with optionals, refer to the Ten Points for Using Optionals post to get yourself up to speed.
Of course, keys are not always literal. We can use a variable or constant. Here we have code to include a constant for the gelato type.:

let scoopSize = 100.0 //grams
let gelatoType = "Pistachio"
if let price = gelato[gelatoType] {
    let extPrice = price * scoopSize
    print("\(scoopSize) grams of "
        + gelatoType
        + " gelato is $\(extPrice)")
}

Change a Dictionary

Suppose we have a dictionary of prices for pizza by a unit area like a square inch, square cm or such. Add this dictionary:

var toppings = ["Pepperoni":0.25,
    "Sausage":0.26,
    "Onions":0.02,
    "Green Peppers":0.03,
    "Cheese":0.01
]

Our cost of Sausage and Cheese increases, and we need to change prices in our dictionary. We can change the value with assignment:

toppings["Sausage"] = 0.29
print(toppings["Sausage"])

Assignment uses subscript syntax and simply changes the value. We can also do this:

toppings.updateValue(0.2, forKey: "Cheese")
toppings["Cheese"]

Line 2 uses the updateValue(value:,forKey:) which does the same as line 1 but the function returns a value. You can write line 2 like this as well:

let oldTopping = toppings.updateValue(0.02,forKey: "Cheese")
if oldTopping == nil{
    print("Key not found")
}

The method returns the value as an optional before we changed it. If there is no key, updateValue(value:,forKey:) returns nil. There is a check to tell the developer that this was not in the dictionary. Unlike an array, a key not found is not a fatal error. Instead it adds an element to the dictionary. If we change a value that isn’t there, such as:

let anotherTopping = toppings.updateValue(0.15,forKey: "Gorgonzola")
toppings["BBQ Chicken"] = 0.24

Swift will add the new entries for Gorgonzola and BBQ Chicken to the dictionary. There are times that we may not want to add a new element, but give an error or do something else. Using updateValue(value:,forKey:)  we can  check for nil. If nil, we  execute code to delete the new value and do whatever we need to for an unknown key.

To delete a dictionary entry, you can set its value to nil:

gelato["Bubble Gum"] = nil
print(gelato)

You can also use the removeValue(forKey:) method

let deletedTopping = gelato.removeValue(forKey:"Peanut Butter")
print(deletedTopping)
print(gelato)

The method removeValue(forKey:) deletes the element. Like updateValue(value:,forKey:) the method returns the value deleted. When deleting an element that does not exist, the method tells you by returning nil, and the subscript syntax does nothing. If you need to know there was no key present, use the method version.

Iterating Dictionaries

Sometimes we need to list or change everything in a dictionary. You can use the for..in loop to do that. If you need to print a list of all entries and their values in the toppings dictionary, create a tuple with identifier names:

for (myKey,myValue) in toppings {
    print("\(myKey) \t \(myValue)")
}

We don’t have optionals involved this time, since all values are existing values. If we need to iterate through just keys or values we can do that using the .keys or .values.
To list all the keys, we could do this:

for myKey  in toppings.keys{
    print ("Key =  \(myKey)")
}

The list that prints may not be in the same order as the list we entered. Dictionaries are unordered collections, and will be near random in order.

We can also do the same thing with the values, but we generally don’t for a really good reason.

for myValue  in toppings.values{
    print ("Value =  \(myValue)")
}

The iterator myValue is a constant inside of the loop. You cannot change this value. For example, if we need to make a 10% increase in all prices for our toppings we can’t write:

for myValue  in toppings.values{
    myValue = myValue * 1.10
}

The compiler would tell us:

Cannot assign to value: 'myValue' is a 'let' constant.

We cannot use the values in the for..in loop to do this. If we want to change values in the loop, we iterate over the keys.  Do the calculation directly to the dictionary value based on the iterated key.

//make a 10% price increase
for myKey  in toppings.keys{
    toppings[myKey] = toppings[myKey]! * 1.10
}

Sometimes we need an array of the keys or values, since a particular API, such as UITableViewController, requires an array. We can use the .keys or .values to create an array:

var valuesArray = [Double](toppings.values)
let keyArray = [String](toppings.keys)

Passing a Dictionary as a Parameter

Sometimes you will need to pass a lot of values of the same type in a method through a parameter. Instead of making a dozen parameters, you can store their values in a dictionary.

func myFunction(dictionary:[String:Double],key:String)-> Double?{
    return dictionary[key]
}

myFunction(gelato,key: "Coconut")

While this really does nothing special, it make a good prototype function. You can see how to declare a dictionary in a function. Declare the dictionary like you would in an explicit let or var statement. Note here I made the return value Double? to let the optional value through.

Here’s One more example returning an extended price.

func extendedPrice(
    dict:[String:Double],
    key:String,
    units:Double
) -> Double?{
    guard let price = dictionary[key] else {return nil}
    return price * units
}

This function has three parameters: the dictionary of the form [String:Double], the key and some units to multiply. Try these:

print( extendedPrice(dict: toppings, key: "Pepperoni", units: 22.0))
print(extendedPrice(dict: gelato, key: "Chocolate", units: 150.0))
print(extendedPrice(dict: piPrice, key: "Android", units: 2.0))

Using different dictionaries, you get results like this, including nil for the not found Android case

Optional(6.0500000000000007)
Option

AnyObject and Dictionaries

Strongly typed dictionaries are the Swift Standard. the value is always the same type. Yet there are some dictionary situations that need multiple types. In some situations, we need something like a struct or class that stores several properties, but it is so temporary we don’t want to declare the class or struct.This is very common in Objective-C written API’s. Consider this function:

func dessertAssembly(cookie: String, iceCream:String) -> [String:String]?{
    let iceCreamServing = 200.0
    var returnDictionary = [String:String]()
    returnDictionary["Dessert"] = cookie + " with " + iceCream
    guard let cookiePrice = mutableCookies[cookie]
        else {return nil}
    guard let gelatoPrice = gelato[iceCream]
        else {return nil}
    returnDictionary["Price"] = cookiePrice + gelatoPrice * iceCreamServing //Double not String
    return returnDictionary
}

We’d like to return for the key Dessert a String and for Price a Double. Swift forces us to return for both keys a string and will give us an error when we assign a Double to our dictionary value.

Binary operator '+' cannot be applied to two 'Double' operands

The compiler expects a string here and thinks + is string concatenation, not addition. It complains you can’t concatenate cookiePrice and gelatoPrice * iceCreamServing since they are doubles.

We could convert the price calculation to a string. We can also do something else: use a dictionary of type [String:AnyObject]. AnyObject allows assignment of any object. If we change the code to this, it compiles without errors:

  
func dessertAssembly(cookie: String, iceCream:String) -> [String:AnyObject]?{
    let iceCreamServing = 200.0
    var returnDictionary = [String:AnyObject]()
    returnDictionary["Cookie"] = cookie + " with " + iceCream
    guard let cookiePrice = mutableCookies[cookie]
        else {return nil}
    guard let gelatoPrice = gelato[iceCream]
        else {return nil}
    returnDictionary["Price"] = cookiePrice + gelatoPrice * iceCreamServing //double not a string
    return returnDictionary
}

Try this:

let myDessert = dessertAssembly(
    cookie: "Chocolate Chip",
    iceCream: "Coconut")
print(myDessert?["Price"])

and we get the price.

As a precaution, many developers downcast to the correct type instead of AnyObject, so they would use

print(myDessert?["Price"] as! Double)

In this example that would need unwrapping the options first, but that my not be a problem in other cases.

Working with TableViews and Dictionaries

As TableViewControllers use IndexPath, they are best used with arrays, not dictionaries. What if you want to use a dictionary with table views? The best way is to create an array of keys, and then use that array. In the code that defined the dictionary, add a computed property to get the keys, in this case for gelato

var keyList:[String] {
    get{
        return [String](gelato.keys)
    }
}

In tableview: cellForRowAt indexpath: for your table, you access your key from keyList, and get the value using that key.

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let row = indexPath.row //get the array index from the index path
    var cell = tableView.dequeueReusableCell(withIdentifier:"tableCell") //make the cell
    if cell == nil {
        cell = UITableViewCell(style: .value1, reuseIdentifier: "cell")
    }
    let myRowKey = keyList[row] //the dictionary key
    cell?.textLabel?.text = myRowKey
    let myRowData = gelato[myRowKey] //the dictionary value
    cell?.detailTextLabel?.text = String(format: "%6.3f",myRowData!)
    return cell!
}

I used the dictionary  starting in the highlighted line 7. Dictionaries are non-ordered, and for an ordered object like a table view, I need something to correspond in an ordered fashion to my table view’s indexPath. I used the computed property typelist, which is an array of keys. This is a good way to get a dictionary ordered when needed. Make an array of keys and iterate over the array pointing to the dictionary. In the highlighted Line 9 I take the key and get the price.

Playgrounds on iPad running the table.
Playgrounds on iPad running the table.

We’ve kept the value’s type to something simple. You can declare a value  as a class instead. In my pizza ordering system, I could have a class describing a pizza order, and use a key to hold the order number. Often people use sequential numbers for the order number. If so, an array might work there. Some systems hide data in the order number, like the date, time or server ID.

Hopefully this was a helpful introduction to Dictionaries in Swift.

The Whole Code

Here is a file of the playground for this lesson. While typing in the code above is very useful you can use this to experiment with dictionaries.

//: Playground - noun: a place where people can play

import UIKit

let cookies = ["Chocolate Chip":0.25,"Oatmeal":0.26]

var mutableCookies = [
"Macadamia Nut":0.06,
"Coconut":0.20,
"Macaron":0.55
]

var gelato = [
    "Coconut":0.025,
    "Pistachio":0.026,
    "Stracciatella":0.002,
    "Chocolate":0.003,
    "Peanut Butter":0.001,
    "Bubble Gum":0.001
]

let piePrice:[String:Double] = [
    "Apple":3.99,
    "Raspberry":3.35
]



let piPrice:Dictionary<String,Double> = [
    "Apple Mac":1200.00,
    "Raspberry Pi":45.00
]

var pieToppings = [String:Double]()


var pizzaSizes = Dictionary<Int, String>()

print(piePrice["Apple"])
print(gelato["Coconut"])
print(piePrice["Coconut Cream"])

print(gelato["Coconut"]! * 100.0)

let slices = 2.0
if piePrice["Apple"] != nil {
    let extPrice = piePrice["Apple"]! * slices
    print("\(slices) slices of Apple Pie is \(extPrice)")
}

if let price = piePrice["Apple"]{
    let extPrice = price * slices
    print("\(slices) slices of Apple Pie is \(extPrice)")
}

let scoopSize = 100.0 //grams
let gelatoType = "Pistachio"
if let price = gelato[gelatoType] {
    let extPrice = price * scoopSize
    print("\(scoopSize) grams of "
        + gelatoType
        + " gelato is $\(extPrice)")
}

//: # Changing a Dictionary

var toppings = ["Pepperoni":0.25,
                "Sausage":0.26,
                "Onions":0.02,
                "Green Peppers":0.03,
                "Cheese":0.01
]

toppings["Sausage"] = 0.29
print(toppings["Sausage"])

toppings.updateValue(0.2, forKey: "Cheese")
print(toppings["Cheese"])


let oldTopping = toppings.updateValue(0.02,forKey: "Cheese")
if oldTopping == nil{
    print("Key not found")
}

let anotherTopping = toppings.updateValue(0.15,forKey: "Gorgonzola")
toppings["BBQ Chicken"] = 0.24

gelato["Bubble Gum"] = nil
print(gelato)

let deletedTopping = gelato.removeValue(forKey:"Peanut Butter")
print(deletedTopping)
print(gelato)

for (myKey,myValue) in toppings {
    print("\(myKey) \t \(myValue)")
}

for myKey  in toppings.keys{
    print ("Key =  \(myKey)")
}


//: Commented out since myValue is a constant
/*for myValue  in toppings.values{
    myValue = myValue * 1.10
}
*/

//: Correct way to itereate over key values
//make a 10% price increase
for myKey  in toppings.keys{
    toppings[myKey] = toppings[myKey]! * 1.10
}

//: Making an array of keys and values
var valuesArray = [Double](toppings.values)
let keyArray = [String](toppings.keys)


//: #Parameters

func extendedPrice(dictionary:[String:Double],key:String,units:Double) -> Double?{
    guard let price = dictionary[key] else {return nil}
    return price * units
}

print( extendedPrice(dictionary: toppings, key: "Pepperoni", units: 22.0))
print(extendedPrice(dictionary: gelato, key: "Chocolate", units: 150.0))
print(extendedPrice(dictionary: piPrice, key: "Android", units: 2.0))

//: AnyObject in action 

//: This does not compile
/*
func dessertAssembly(cookie: String, iceCream:String) -> [String:String]?{
    let iceCreamServing = 200.0
    var returnDictionary = [String:String]()
    returnDictionary["Cookie"] = cookie + " with " + iceCream
    guard let cookiePrice = cookies[cookie]
        else {return nil}
    guard let gelatoPrice = gelato[iceCream]
        else {return nil}
    returnDictionary["Price"] = cookiePrice + gelatoPrice * iceCreamServing //double not a string
    return returnDictionary
}
 */


//this does compile
func dessertAssembly(cookie: String, iceCream:String) -> [String:AnyObject]?{
    let iceCreamServing = 200.0
    var returnDictionary = [String:AnyObject]()
    returnDictionary["Cookie"] = cookie + " with " + iceCream
    guard let cookiePrice = cookies[cookie]
        else {return nil}
    guard let gelatoPrice = gelato[iceCream]
        else {return nil}
    returnDictionary["Price"] = cookiePrice + gelatoPrice * iceCreamServing //double not a string
    return returnDictionary
}

let myDessert = dessertAssembly(
    cookie: "Chocolate Chip",
    iceCream: "Coconut")
print(myDessert?["Price"])

//: Table View Controller

var keyList:[String] {
    get{
        return [String](gelato.keys)
    }
}
//comment out on IBM Sandbox. 
class TableViewController:UITableViewController{
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return gelato.count
    }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let row = indexPath.row //get the array index from the index path
    var cell = tableView.dequeueReusableCell(withIdentifier:"tableCell") //make the cell
    if cell == nil {
        cell = UITableViewCell(style: .value1, reuseIdentifier: "cell")
    }
    let myRowKey = keyList[row] //the dictionary key
    cell?.textLabel?.text = myRowKey
    let myRowData = gelato[myRowKey] //the dictionary value
    cell?.detailTextLabel?.text = String(format: "%6.3f",myRowData!)
    return cell!

}

}

import PlaygroundSupport
let vc = TableViewController()
PlaygroundPage.current.liveView = vc
PlaygroundPage.current.needsIndefiniteExecution = true



5 responses to “How to Use Dictionaries in Swift 3.0”

  1. Very useful to understand this subject. Thank you!

    I tried to use the table view part to create a key for my plist data (dictionaries in a dictionary -> myDict = [[“key1”: “string1”, “key2”: “string2”, “key3”: “string3”]]), but I get an error saying that myDict (of Type ) has no keys (in the return line):

    var keyList:[String] {
    get {
    return [String](myDict.keys)
    }
    }

    How can I solve this?

    1. myDict is a array of dictionary [String:String] because you have a double brace. It has one element. Either get the first element of the array then look in the dictionary or get rid of the double brace.

  2. When you want to send data from ChildViewController to ParentViewcontroller, you have to do with delegation as above described. Nice tutorials,

  3. Thanks! I solved a problem following this tip: `As a precaution, many developers downcast to the correct type instead of AnyObject`.

    1. You are welcome. Down casting solves many a problem, thoug I should really update this to Any? Take a look here for more on this subject. https://makeapppie.com/2018/05/09/tip-deep-dive-into-any-dictionaries/

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 )

Facebook photo

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

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: