Advanced and Practical Enum usage in Swift

Custom Data Types

Custom Data Types

If we neglect associated values, then the value of an enum can only be an Integer, Floating Point, String, or Boolean. If you need to support something else, you can do so by implementing the ExpressibleByStringLiteral protocol which allows the type in question to be serialized to and from String.

As an example, imagine you'd like to store the different screen sizes of iOS devices in an enum:

enum Devices: CGSize {
   case iPhone3GS = CGSize(width: 320, height: 480)
   case iPhone5 = CGSize(width: 320, height: 568)
   case iPhone6 = CGSize(width: 375, height: 667)
   case iPhone6Plus = CGSize(width: 414, height: 736)
}

However, this doesn't compile because CGSize is not a literal and can't be used as an enum value. Instead, what you need to do is add a type extension for the ExpressibleByStringLiteral protocol.

The protocol requires us to implement an initializer that receives a String. Next, we need to take this String an convert it into a CGSize. Not any String can be a CGSize. So if the value is wrong, we will crash with an error as this code will be executed by Swift during application startup. Our string format for sizes is: width,height

extension CGSize: ExpressibleByStringLiteral {
    public init(stringLiteral value: String) {
        let components = rawValue.split(separator: ",")
        guard components.count == 2,
            let width = Int(components[0]),
            let height = Int(components[1])
            else { return fatalError("Invalid Format \(value)") }
        self.init(width: size.width, height: size.height)
    }
}

Now, we can write our enum, with one downside though: The initial values have to be written as a String, since that's what the enum will use (remember, we complied with ExpressibleByStringLiteral, so that the String can be converted to our CGSize type.

enum Devices: CGSize {
   case iPhone3GS = "320,480"
   case iPhone5 = "320,568"
   case iPhone6 = "375,667"
   case iPhone6Plus = "414,736"
}

This, finally, allows us to use our CGSize enum. Keep in mind that in order to get the actual CGSize value, we have to access the rawValue of the enum.

let a = Devices.iPhone5
let b = a.rawValue
print("the phone size string is \(a), width is \(b.width), height is \(b.height)")

This works, because we explicitly told Swift that a CGSize can be created from any String.

A different option to hook into custom types it the RawRepresentable protocol, we will tackle this next.