Up to now, you have accessed the assets you added to your apps through Interface Builder, but assets can also be accessed programmatically. Each image and color you added to the Asset Catalog has a name, which is how you reference it in code:
let happyImage: UIImage? = UIImage(named: "happy") let happyColor: UIColor? = UIColor(named: "happyTurquoise")
Note that the resources here are looked up by their names, which are strings. If you were to enter a resource name in your code incorrectly, the resource would not be located at runtime. Also, because there may not be a resource associated with a given string (even if it is entered correctly), these initializers are failable and therefore must return an optional.
In short, accessing assets programmatically is bug prone. Wouldn’t it be nice to have some help from the compiler to avoid these bugs? What if – just for example – you could access your images like this?
let happyImage: UIImage = UIImage(resource: .happy) let happyColor: UIColor = UIColor.happy
By using a static variable instead of a string to identify resources, you allow the compiler to validate your code as you enter it and generate an error if you make a mistake. This gives you more confidence in your code, guarantees that you will not look up a resource that is not there at runtime, and adds the perk of code completion. Sounds great, right?
It is great, and you can achieve this wonder using an extension. Extensions serve a couple of purposes: They allow you to group chunks of functionality into a logical unit, and they allow you to add functionality to your own classes, structs, and enums as well as types provided by the system or other frameworks. Being able to add functionality to a type whose source code you do not have access to is a very powerful and flexible tool.
Let’s take a look at an example. With extensions, you can add methods and computed properties (but not stored properties) to types. Say you wanted to add functionality to the Int type to provide a doubled value, like this:
let fourteen = 7.doubled // The value of fourteen is '14'
You can add this functionality by extending the Int type:
extension Int { var doubled: Int { return self * 2 } }
Extensions can also make your code more readable and help with long-term maintainability of your code base by grouping related pieces of functionality. One common chunk of functionality that is often grouped into an extension is conformance to a protocol along with implementations of the protocol’s methods.
Enough talk. You are going to create an extension on UIColor to make it easier to access your custom color assets. Create a new Swift file and name it UIColor+Mandala. Conventionally, extensions are named with the type you are extending (UIColor here), followed by a + and some description of the extension.
In UIColor+Mandala.swift, declare your UIColor extension.
Listing 17.3 Declaring a UIColor
extension (UIColor+Mandala.swift
)
import Foundationimport UIKit extension UIColor { }
You are going to follow the same pattern that UIKit provides for your new colors by making them static properties on UIColor. So just as you are able to use UIColor.green
, you will be able to use UIColor.happy
.
Add the new color static properties within the UIColor extension.
Listing 17.4 Adding new colors (UIColor+Mandala.swift
)
import UIKit extension UIColor { static let angry = UIColor(named: "angryRed")! static let confused = UIColor(named: "confusedPurple")! static let crying = UIColor(named: "cryingLightBlue")! static let goofy = UIColor(named: "goofyOrange")! static let happy = UIColor(named: "happyTurquoise")! static let meh = UIColor(named: "mehGray")! static let sad = UIColor(named: "sadBlue")! static let sleepy = UIColor(named: "sleepyLightRed")! }
Here, you are using the initializer that takes in a name, UIColor(named:), and then force unwrapping the value that is returned. This is fine to do since it is a programmer error if you misspell the resource name. You have to use the string in exactly one place in the application, in this file, and now elsewhere you can reference these colors via the static properties on UIColor.
You are going to do something very similar for the UIImage extension. Create a new Swift file and name it UIImage+Mandala.swift. Open this file and declare a UIImage extension.
Listing 17.5 Declaring a UIImage
extension (UIImage+Mandala.swift
)
import Foundationimport UIKit extension UIImage { }
Instead of creating static properties on UIImage, you are going to create a new initializer that takes in an enumeration value that corresponds to each image resource. Create this enumeration near the top of UIImage+Mandala.swift.
Listing 17.6 Implementing the ImageResource
enumeration (UIImage+Mandala.swift
)
enum ImageResource: String { case angry case confused case crying case goofy case happy case meh case sad case sleepy } extension UIImage { }
Notice that this enumeration is backed by a String raw value. You will use the strings associated with each case to look up the corresponding image resource.
Now implement a new convenience initializer that accepts an ImageResource instance.
Listing 17.7 Implementing a new UIImage
initializer (UIImage+Mandala.swift
)
extension UIImage { convenience init(resource: ImageResource) { self.init(named: resource.rawValue)! } }
This is similar to the UIColor extension in some ways; you are (indirectly) using a string value in exactly one place, and then force unwrapping the result of the initializer that you are chaining to. Now you can use this initializer elsewhere in your application with confidence that you are not making a mistake.
It is time to put these pieces together and create your various moods.
Open Mood.swift and declare an extension at the bottom. This extension will be used to group all the static moods.
Listing 17.8 Adding an extension to the Mood
type (Mood.swift
)
struct Mood { var name: String var image: UIImage var color: UIColor } extension Mood { }
Now declare static properties for each of the moods. You will use the two extensions that you declared earlier to create these moods.
Listing 17.9 Adding static moods (Mood.swift
)
extension Mood { static let angry = Mood(name: "angry", image: UIImage(resource: .angry), color: UIColor.angry) static let confused = Mood(name: "confused", image: UIImage(resource: .confused), color: UIColor.confused) static let crying = Mood(name: "crying", image: UIImage(resource: .crying), color: UIColor.crying) static let goofy = Mood(name: "goofy", image: UIImage(resource: .goofy), color: UIColor.goofy) static let happy = Mood(name: "happy", image: UIImage(resource: .happy), color: UIColor.happy) static let meh = Mood(name: "meh", image: UIImage(resource: .meh), color: UIColor.meh) static let sad = Mood(name: "sad", image: UIImage(resource: .sad), color: UIColor.sad) static let sleepy = Mood(name: "sleepy", image: UIImage(resource: .sleepy), color: UIColor.sleepy) }
Build the project to confirm that you have not introduced any errors.
You have used extensions to add capabilities to both the UIColor and UIImage classes as well as to group the various moods. Now that Mandala has its list of moods to use, it is time to start setting up the interface.