As developers who write code for the Apple ecosystem in Swift, we use enums all the time in order to represent lists of values that are meaningful to our programs. Enums can contain simple cases, but quite often there are raw values matching to each case. As an example, see the following that represents week days; each day has been assigned with an integer number starting from 1:
1 2 3 4 5 6 7 8 9 10 11 |
enun Weekdays: Int { case monday = 1 case thusday case wednesday case thursday case friday case saturday case sunday } |
Enums with raw values just like the above are pretty handy in programming. We can easily get the raw value of a case using the rawValue
property, as well as to get a case based on a raw value. And that is because an enum with raw values automatically conforms to RawRepresentable protocol once we declare the value type.
Usually, raw values are of basic Swift data types, such as Int, String, Double, etc. However, it’s absolutely possible to create enums with raw values of custom types as well. There are a couple of rules to follow in order to manage that, but nothing complicated or confusing. That said, let’s find out what it takes to create an enum with a custom raw type.
Preparing a custom type
Let’s suppose that we want to represent the status of a task visually, and that there is a different color to show for each status case. An enum is obviously the perfect choice to keep all various cases programmatically. But for the purposes of this demonstration, we’ll also create a custom type for representing a status color.
Being specific, this new type will be containing a string property which will be representing a color as a hexadecimal value. In addition, a computed property will be returning the actual color object, so we can use it later in a view.
Here’s the implementation of all that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct StatusColor { var hex: String var color: NSColor { var intHex: UInt64 = 0 let scanner = Scanner(string: hex) scanner.scanHexInt64(&intHex) let red = CGFloat((intHex & 0xFF0000) >> 16) / 255.0 let green = CGFloat((intHex & 0xFF00) >> 8) / 255.0 let blue = CGFloat((intHex & 0xFF)) / 255.0 return NSColor(red: red, green: green, blue: blue, alpha: 1.0) } } |
Note: Instead of NSColor, A UIColor, or a SwiftUI Color could be returned as well from the color
property above. I just chose to demonstrate today’s topic in a macOS project.
I mentioned earlier that there are certain rules to follow when it’s necessary to create enums with custom raw types. In fact, there are two of them, and they regard the custom type only.
The first rule is that our custom type should be conforming to the Equatable
protocol:
1 2 3 4 5 |
struct StatusColor: Equatable { … } |
Note that you might need to implement the ==(lhs:rhs:)
protocol’s method manually if the data types of the stored properties do not conform to Equatable
already. This is not the case here, as hex
is a String value, and String conforms to Equatable
.
The second rule to respect is that the custom type should also be conforming to any of the following protocols, so it can be expressed as a string, integer, or float literal respectively:
- ExpressibleByStringLiteral
- ExpressibleByIntegerLiteral
- ExpressibleByFloatLiteral
In the specific example of this post the StatusColor
type must conform to ExpressibleByStringLiteral
protocol:
1 2 3 4 5 |
struct StatusColor: Equatable, ExpressibleByStringLiteral { … } |
By doing that, it becomes mandatory to implement the following initializer in our type as well:
1 2 3 4 5 6 7 8 9 |
struct StatusColor: Equatable, ExpressibleByStringLiteral { … init(stringLiteral value: StringLiteralType) { hex = value } } |
The StatusColor
type is now ready to be used as the raw type in enums.
An enum with the StatusColor raw type
The following enum represents the various statuses of a task:
1 2 3 4 5 6 7 8 |
enum StatusIndication { case inProgress case completed case stopped case failed } |
Right now there is no raw type declared, but we’ll change that by setting the StatusColor
implemented right above. In addition to that, we’ll also specify the desired color for each case. But we won’t assign StatusColor
instances as values; since our type conforms to ExpressibleByStringLiteral protocol, we’ll just assign hex string values as shown next, and the compiler will take care of the rest:
1 2 3 4 5 6 7 8 |
enum StatusIndication: StatusColor { case inProgress = “0000ff” case completed = “00ff00” case stopped = “181818” case failed = “ff0000” } |
Putting in motion the enum with the custom raw type
In a SwiftUI view now, we’re going to give a try to the above enum in order to visually show the various statuses along with the matching color. The first thing we’ll do is to define an assistive method marked with the @ViewBuilder
attribute. The method will return a SwiftUI view, an HStack in particular, which will be containing a title for the status and a color. Here’s its implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@ViewBuilder func status(withTitle title: String, statusIndication: StatusIndication) -> some View { HStack { Text(title) .fontWeight(.medium) .frame(width: 100) Color(statusIndication.rawValue.color) .frame(width: 150, height: 100) } } |
See that both the status title, as well as the indication are given as arguments. But more important than that is to notice how we access the actual color through the given StatusIndication
case; we first access its raw value, which is a ColorStatus
object, and then through that the color
computed property:
1 2 3 |
statusIndication.rawValue.color |
In the view’s body we will call the status(withTitle:statusIndication:)
method as many times as the various statuses, passing each time a different case:
1 2 3 4 5 6 7 8 9 10 11 |
var body: some View { VStack { status(withTitle: “In progress”, statusIndication: .inProgress) status(withTitle: “Completed”, statusIndication: .completed) status(withTitle: “Stopped”, statusIndication: .stopped) status(withTitle: “Failed”, statusIndication: .failed) } .frame(width: 350, height: 500) } |
By running this simple app we get this:
Of course, and as it also happens with enums with built-in raw types, we are able to get the case of an enum by providing the raw value. For instance, take a look at the following; at first we create a new StatusColor
object, and then we pass it as the raw value to a new StatusIndication
value:
1 2 3 4 5 6 7 8 |
let statusColor = StatusColor(stringLiteral: “00ff00”) let indication = StatusIndication(rawValue: statusColor) print(“The status is:”, indication!) // It prints: // The status is: completed |
Watch out however not to provide a raw value that does not match to an actual case; if that happens, the StatusIndication
initialization will return a nil value.
Overview
Enums with custom raw types can become a quite powerful tool that can lead in turn to smarter and less code. When creating your own enums with custom types, make sure to comply to the two simple requirements presented previously, and the rest is easy. The simple scenario demonstrated in this post shows all you need to know in order to start using that technique in your own projects, so just go ahead and give it a try.
Thank you for reading, enjoy coding! ????????????