Whether you pronounce it the correct way (e-num) or the other way (e-noom), there's no avoiding enums in Swift, they're everywhere - and for good reason! Enums enable us to map groups of values and use them in a type-safe way.

Swift enums work differently to counterparts you may have used in other C-based languages, they don't have to directly map to an underlying type (although they can), but are types in their own right.

Defining an Enum

Enums are defined using the enum keyword, followed by the name. The swift naming convention for enums is the same as a class or struct (camel case with the first letter capitalised). The name of the enum should always be the singular reference to what your defining, not the plural; for example, use Device instead of Devices. This may seem counterintuitive at first as you're defining a group of values, but when referencing the enum we would use Device.iphone, which makes more sense in practice than Devices.iphone.

We define values for the enum using the case keyword.

enum Device {
    case iphone
    case ipad
    case macbook
}

let iphone = Device.iphone
// or
let iphone: Device = .iphone

Enums can specify an underlying type (much like traditional enums are ints), these are called raw values, they can be an int, float or String and can be accessed by the rawValue property. Conforming to a type also gives us the init?(rawValue: _) initialiser to instantiate our enums directly using the raw value. Notice that this initialiser is optional, if the compiler can't map the rawValue to an enum it will return nil.

enum Device: String {
    case iphone
    case ipad	
    case macbook
}

let d1 = Device(rawValue: "iphone")
print(d1)
// iphone

let d2 = Device(rawValue: "iPhone")
print(d2)
// nil

print(d1.rawValue)
// iphone

if we don't want to use the name of the enum case as the value, we can specify our own.

enum Device: String {
    case iphone = "iPhone"
    case ipad = "iPad"
    case macbook = "Macbook"
}

let d1: Device = .iphone
print(d1.rawValue)
// iPhone

This is particularly useful when dealing with integers, for example we could map HTTP error codes to an enum.

case ServerError: Int {
    case notFound = 404
    case serverError = 500
    ...
}

By default when using Int as our underlying type the initial case is 0 and increments by 1 for each case. It's worth noting that it will always increment by 1 starting from the first case you provide, so if you want to start from 1 and add one for each case you only need you specify the value for the first case.

enum Number {
    case one = 1
    case two // 2
    case three // 3
    ...
}

Pattern Matching

A common way to match individual enum case values is with a switch statement.

let device: Device = .macbook
switch(device) {
case .iphone:
    print("iPhone")
case .ipad:
    print("iPad")
case .macbook:
    print("Macbook")        
}

// Macbook

When we use an enum in conjunction with a switch statement, we need to either handle all cases (as above) or provide a default case, else we get the compiler error: Switch must be exhaustive.

let device: Device = .macbook
switch(device) {
case .iphone:
    print("iPhone")
case .ipad:
    print("iPad")
case default:
    print("Macbook")        
}

// Macbook

This is a nice reminder from the swift compiler to handle all potential cases!

If you want to match a single case (my rule is no more than 3!) we could also use an if statement here.

let device: Device = .macbook
if device == .macbook {
    // Do something
}

Associated Values

An enum case can have an associated value of any type, you can think of this like passing a parameter to a function. If you need a good example of when and why this would be useful you can look no further than my last post on the Result type, which is in itself an enum that makes use of associated values with generic types.

enum OperatingSystem {
    case ios
    case ipados
    case macos
}

enum Device {
    case iphone(OperatingSystem)
    case ipad(OperatingSystem)
    case macbook(OperatingSystem)    
}

let os: OperatingSystem = .ios
let device: Device = .iphone(os)

switch(device) {
case .iphone(let os):
    print("iPhone \(os)")
case .ipad(let os):
    print("iPad \(os)")
case .macbook(let os):
    print("Macbook \(os)")
}

// iPhone ios

Accessing all cases

As long as your enum has no associated value you can access all cases in an array, all we need to do is have our enum conform to the CaseIterable protocol, this gives us access to allCases. Before this change we had to define allCases manually by adding each case to an array, not ideal as it could easily lead to missed cases should our enum evolve.

enum Device: CaseIterable {
    case iphone
    case ipad
    case macbook
}

Device.allCases
// [iphone, ipad, macbook]

This comes in surprisingly useful at times!

Computed Properties

As enums in swift are concrete types they share a lot of functionality with structs and classes (some of which we've demonstrated), we can conform to protocols, write custom initialisers and use computed properties.

enum Device {	
    case iphone
    case ipad
    case macbook
    
    var name: String {
    	switch(self) {
     	case .iphone:
            return "iPhone"
        case .ipad:
            return "iPad"
        case .macbook:
            return "MacBook"
        }
    }
}

Encapsulating logic inside an enum like this promotes good separation of concerns, before we may have had to contain that logic inside a helper function or view model, now we can keep all related logic for Device in one place. We can extend this example further.

enum Device {	
    case iphone
    case ipad
    case macbook
}

extension Device {
    var name: String {
    	switch(self) {
        case .iphone:
            return "iPhone"
        case .ipad:
            return "iPad"
        case .macbook:
            return "MacBook"
        }
    }
}

Now we've separated our business logic out into an extension and left our declaration looking clean.


Swift really does change the game for enums, elevating them to concrete types from the underlying ints we've used in other C-based languages, they really are a fantastically diverse feature of Swift. Further reading on enums can be found in the docs .

Thanks for reading ✌️