I have mentioned enumerations in three previous posts (Computed Properties in Swift, Swift Optionals – Declaration, Unwrapping, and Binding, and Loops, Switch Statements, and Ranges in Swift). It is probably about time to actually talk about them, eh? In C, and even Objective-C, enumerations were little more than glorified aliases for integer values. In Swift though, enumerations have been given significantly more power. In Swift, enumerations are a lot more like classes or structs, on top of the actual enumeration values themselves. For the moment though, we will talk about what makes an enumeration an enumeration.
Declaring and Defining Enumerations
First thing’s first, how do you declare an enumeration in Swift? You use the enum keyword, the name of the enumeration, and put a series of cases inside the enumeration’s curly braces (somewhat similar to a switch statement), like so:
enum LevelProgress { case Completed case AttemptedButIncomplete case Unattempted }
Back in the article Swift Optionals – Declaration, Unwrapping, and Binding, I had mentioned that enumerations can have associated values, in that case being the value that the optional was encapsulating. So this enum was actually written to show those off a bit. If we had a game that showed icons for the levels, and have them change based on how far the player has been, we could rewrite the above enumeration like this:
enum LevelProgress{ case Completed(Int) case AttemptedButIncomplete(Double, NSDate) case Unattempted }
So for this I had a Completed state (with the number of stars earned), an AttemptedButIncomplete state (with the percentage complete and the date last attempted), and an Unattempted case (with no values), since they haven’t even tried it. You would then assign it to a variable like this:
var level1 = LevelProgress.Completed(3)
Also, if the type can be inferred with Swift’s type inference, you don’t even need to use the name of the enum (in this case LevelProgress). You do have to if it cannot be easily inferred. If it is already defined as a LevelProgress type, the name of the enum can be omitted, so you can do this:
var level1 = LevelProgress.Completed(3) level1 = .AttemptedButIncomplete(0.75, testDate)
Where you defined it as a valid enumeration variable once, or you could explicitly give it a type like this:
var level2 : LevelProgress level2 = .Completed(3)
Also, if a function parameter is of a certain type, like LevelProgress, you also can omit the name of the enum, since only a value of that type can be used as that argument. So, if we had a function signature like this:
func printLevelDetails(level: LevelProgress)
We could call it with an instance of type levelProgress, or we could just send the value in directly:
printLevelDetails( .Completed(3) )
Labelled Enumeration Initializers
Now, I couldn’t find anything in the Apple iBook The Swift Programming Language, nor really online about this, but I thought that similar to classes, you would be be able to name these inputs. If nothing else, it should make it more readable. Well, at least from messing around on a playground, it appears like you can, so I rewrote this like so:
enum LevelProgress { case Completed(stars: Int) case AttemptedButIncomplete(percentComplete: Double, lastAttemptedDate: NSDate) case Unattempted }
If I try to instantiate these states without those labels, the playground complains and suggests adding the labels in, as it should, so maybe an undocumented feature in Swift? Who knows, but its there. If I had to guess, it probably is calling an internal init method to assign this enumeration to a variable, but I have no evidence of this yet. Since initializers can give their input parameters labels, this seems to make sense to me. This may be a side effect, and there may be a reason they don’t mention it in the iBook, so, use it at your own risk. This appears to still work as of Xcode 7.3.1.
For this style, you would then assign it to a variable like this:
let level1 = LevelProgress.Completed(stars: 3)
Using Enumerations
So, as I mentioned in Loops, Switch Statements, and Ranges in Swift, enumerations are particularly useful in switch statements. As mentioned in that article, switch statements must be exhaustive, so it must cover all of the enumeration states, or define a default state to do so. So as such, let us give some functionality to that function I mentioned at the end of the Declaring and Defining Enumerations section. In it we have a switch statement that also takes advantage of the associated values.
func printLevelDetails(level: LevelProgress) { switch level { case .Unattempted: print("Didn't attempt") case .AttemptedButIncomplete(let percentComplete, let lastAttemptedDate): print("About \(percentComplete) complete, last tried \(lastAttemptedDate).") case .Completed(let stars): print("Complete with \(stars) stars.") } }
So, basically, your switch statement runs through each case of the enum, and does something based off of it. In this case, for the AttemptedButIncomplete and Completed states, it then binds the associated values to a variable that we then use in the println call. All you have to do to bind it to a variable is to say “let newVariableName” for each value, in the order they are used.
I have used this a few times, but not explicitly stated it, that “\(someVariable)” notation is a shorthand in Swift for printing the string representation of a variable.
Without using an NSNumberFormatter, the output for .AttemptedButIncomplete is rather ugly, for example: “About 0.75 complete, last tried 2014-08-01 20:09:58 +0000.” It does get the point across for how to use it though.
One thing to note, if you try the labelled associated value names mentioned in the last section, these are NOT the same. As of yet, I have not seen a way to use those labels to get the values back out explicitly through the label. These are completely different names that do not interact with them at all, according to my testing.
Raw Values For Enumerations
There are cases when you need values with your enumeration states, but not the full flexibility of associated values. You can use raw values if:
- Each case will have only 1 value.
- That value will be the same type for each.
- That value can be pre-populated in the enumerations definition.
- That value will be unique for each case.
- That value is a literal (so a String, Character, or the built in Integer and Floating-Point types).
As an extra, if an Integer is used, it will auto-increment for each value if it is not explicitly defined.
This is Swift’s way of implementing enumerations in a way more consistent to how they were in C or Objective-C
Defining an Enumeration with Raw Values
This is actually pretty easy. You basically give the enum a type after its name (like you would for a variable), and then equate the case names to a literal of some sort, so:
enum FirstYearOfDecade: Int { case Seventies = 1970 case Eighties = 1980 case Nineties = 1990 }
This follows all of the conventions mentioned above. Each has one value (the first year of that decade), they are all the same type (Int), they were pre-populated in the definition, they are unique, and each set to a literal (in this case an Integer Literal).
Using the Raw Type
Apple made this pretty easy with a simple property to read it, and the singular parameter to write it.
So to read the raw value, we could do either of these:
FirstYearOfDecade.Eighties.rawValue let instantiatedDecade = FirstYearOfDecade.Seventies instantiatedDecade.rawValue
The first returns the integer associated with the “Eighties” case. The second one assigns the “Seventies” case to a constant, and then we ask that constant to return the raw value with .rawValue.
To create a value from a raw value, you create it through an initializer. These initializers are actually failable initializers, so they return an optional, since you may not always find a valid value from the raw version. For example, this enumeration is actually talking about the first year of the decade, if I try 1991, since it is not the first year of 1990, it will return nil for this optional. To safely use init?(rawValue: Int), you can use Optional Binding:
if let otherDecade = FirstYearOfDecade(rawValue: 1990) { switch otherDecade { case .Nineties: print("I love the 90s") default: print("What decade are you talking about?") } } else { print("Couldn't find it") }
Conclusion
Those are the basics to using enumerations in Swift. I did mention earlier that they are much more similar to classes in Swift than they were in C or Objective-C. This allows them to also have methods, properties (besides the associated values), and more. For now though, I just wanted to cover what makes enumerations different, as opposed to how it is similar to its relatives. You can read more about classes in the post Classes In Swift — An Introduction, and structures in the post Structures in Swift.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!