Using enumerations is one of the best practices that you should follow while writing any software project and not only iOS projects. Once you find that you have a group of related values in your project, create enum to group these values and to define a safe type for these values. With enumerations, your code becomes more readable and easy to understand, as it makes you define new types in your project that map to other value. In Swift, enumerations have been taken care of and have become more flexible than the ones used in other programming languages.
Creating enumerations to write readable code
Getting ready
Now, we will dive into enumerations and get our hands dirty with it. To do so, we will create a new playground file in Xcode called Enumerations so that we can practice how to use enumerations and also see how it works.
Writing enumerations is meant to be easy, readable, and straightforward in syntax writing in Swift. Let's see how enum syntax goes:
enum EnumName{ }
You see how it's easy to create enums; your enumeration definition goes inside the curly braces.
How to do it...
Now, let's imagine that we are working on a game, and you have different types of monsters, and based on the type of monster, you will define power or the difficulty of the game. In that case, you have to use enum to create a monster type with different cases, as follows:
- Type the following code to create enum with name Monster:
enum Monster{ case Lion case Tiger case Bear case Crocs } enum Monster2{ case Lion, Tiger, Bear, Crocs }
- Use the '.' operator to create enums variables from the previously created enum:
var monster1 = Monster.Lion let monster2 = Monster.Tiger monster1 = .Bear
- Use the switch statement to check the value of enum to perform a specific action:
func monsterPowerFromType(monster:Monster) ->Int { var power = 0 switch monster1 { case .Lion: power = 100 case .Tiger: power = 80 case .Bear: power = 90 case .Crocs: power = 70 } return power } let power = monsterPowerFromType(monster1) // 90 func canMonsterSwim(monster:Monster) ->Bool{ switch monster { case .Crocs: return true default: return false } } let canSwim = canMonsterSwim(monster1) // false
How it works...
Now, you have a new type in your program called Monster, which takes one value of given four values. The values are defined with the case keyword followed by the value name. You have two options to list your cases; you can list each one of them in a separate line preceded by the case keyword, or you can list them in one line with a comma separation. I prefer using the first method, that is, listing them in separate lines, as we will see later that we can add raw values for cases that will be more clear while using this method.
The first variable monster1 is created using the enum name followed by '.' and then the type that you want. Once monster1 is initialized, its type is inferred with Monster; so, later you can see that when we changed its value to Bear, we have just used the '.' operator as the compiler already knows the type of monster1. However, this is not the only way that you will use enums. Since enums is a group of related values, so certainly you will use it with control flow to perform specific logic based on its value. The switch statement is your best friend in that case as we saw in the monsterPowerFromType() function.
We've created a function that returns the monster power based on its type. The switch statement checks all values of monster with '.' followed by an enum value. As you already know, the switch statement is exhaustive in Swift and should cover all possible values; of course, you can use default in case it's not possible to cover all, as we saw in the canMonsterSwim() function. The default statement captures all non-addressed cases.
There's more...
Enumerations in Swift have more features, such as using enums with raw values and associated values.
Enum raw values
We saw how enums are defined and used. Enum cases can come with predefined values, which we call raw values. To create enums with raw values, the following rules should be adhered:
- All raw values should be in the same type.
- Inside the enum declaration, each raw value should be unique.
- Only possible values allowed to use are strings, characters, integer, and floating point numbers.
Assigning raw values
When you assign raw values to enum, you have to define the type in your enum syntax and give value for each case:
enum IntEnum: Int{ case case1 = 50 case case2 = 60 case case3 = 100 }
Swift gives you flexibility while dealing with raw values. You don't have to explicitly assign a raw value for each case in enums if the type of enum is Int or String. For Int type enums, the default value of enum is equal to the value of previous one + 1. In case of the first case, by default it's equal to 0. Let's take a look at this example:
enum Gender: Int{ case Male case Female case Other } var maleValue = Gender.Male.rawValue // 0 var femaleValue = Gender.Female.rawValue // 1
We didn't set any raw value for any case, so the compiler automatically will set the first one to 0, as it's a no set. For any following case, it's value will be equal to previous case value + 1. Another note is that .rawValue returns the explicit value of the enum case. Let's take a look at another complex example that will make it crystal clear:
enum HTTPCode: Int{ case OK = 200 case Created // 201 case Accepted // 202 case BadRequest = 400 case UnAuthorized case PaymentRequired case Forbidden } let pageNotFound = HTTPCode.NotFound let errorCode = pageNotFound.rawValue // 404
We have explicitly set the value of first case to 200; so, the following two cases will be set to 201 and 202, as we didn't set raw values for them. The same will happen for BadRequest case and the following cases. For example, the NotFound case is equal to 404 after incrementing cases.
Now, we see how Swift compiler deals with Int type when you don't give explicit raw values for some cases. In case of String, it's pretty easier. The default value of String enum cases will be the case name itself. Let's take a look at an example:
enum Season: String{ case Winter case Spring case Summer case Autumn } let winter = Season.Winter let statement = "My preferred season is " + winter.rawValue // "My preferred season is Winter"
You can see that we could use the string value of rawValue of seasons to append it to another string.
Using Enums with raw values
We saw how easy it is to create enums with raw values. Now, let's take a look at how to get the raw value of enums or create enums back using raw values.
We already saw how to get the raw value from enum by just calling .rawValue to return the raw value of the enum case.
To initialize an enum with a raw value, the enum should be declared with a type; so in that case, the enum will have a default initializer to initialize it with a raw value. An example of an initializer will be like this:
let httpCode = HTTPCode(rawValue: 404) // NotFound let httpCode2 = HTTPCode(rawValue: 1000) // nil
The rawValue initializer always returns an optional value because there will not be any matching enum for all possible values given in rawValue. For example, in case of 404, we already have an enum whose value is 404. However, for 1000, there is no enum with such value, and the initializer will return nil in that case. So, before using any enum initialized by the rawValue initializer, you have to check first whether the value is not equal to nil; the best way to check for enums after initialization is by this method:
if let httpCode = HTTPCode(rawValue: 404){ print(httpCode) } if let httpCode2 = HTTPCode(rawValue: 1000){ print(httpCode2) }
The condition will be true only if the initializer succeeds to find an enum with the given rawValue.
Enums with associated values
Last but not least, we will talk about another feature in Swift enums, which is creating enums with associated values. Associated values let you store extra information with enum case value. Let's take a look at the problem and how we can solve it using associated values in enums.
Suppose we are working with an app that works with products, and each product has a code. Some products codes are represented by QR code format, but others by UPC format. Check out the following image to see the differences between two codes at http://www.mokemonster.com/websites/erica/wp-content/uploads/2014/05/upc_qr.png):
The UPC code can be represented by four integers; however, QR code can be represented by a string value. To create an enum to represent these two cases, we would do something like this:
enum ProductCode{ case UPC(Int, Int, Int, Int) case QR(String) } var productCode = ProductCode.UPC(4, 88581, 1497, 3) productCode = ProductCode.QR("BlaBlaBla")
First, UPC is a case, which has four integer values, and the second is a QR, which has a string value. Then, you can create enums the same way we created before in other enums, but here you just have to pass parameters for the enum. When you need to check the value of enum with its associated value, we will use a switch statement as usual, but with some tweaks:
switch productCode{ case .UPC(let numberSystem, let manufacturerCode, let productCode, let checkDigit): print("Product UPC code is \(numberSystem) \(manufacturerCode) \(productCode) \(checkDigit)") case .QR(let QRCode): print("Product QR code is \(QRCode)") } // "Product QR code is BlaBlaBla"