Can Swift enums have multiple raw values?

24,498

Solution 1

No, an enum cannot have multiple raw values - it has to be a single value, implementing the Equatable protocol, and be literal-convertible as described in the documentation.

I think the best approach in your case is to use the error code as raw value, and a property backed by a prepopulated static dictionary with the error code as key and the text as value.

Solution 2

You have a couple options. But neither of them involve raw values. Raw values are just not the right tool for the task.

Option 1 (so-so): Associated Values

I personally highly recommend against there being more than one associated value per enum case. Associated values should be dead obvious (since they don't have arguments/names), and having more than one heavily muddies the water.

That said, it's something the language lets you do. This allows you to have each case defined differently as well, if that was something you needed. Example:

enum ErrorType {
    case teapot(String, Int)
    case skillet(UInt, [CGFloat])
}

Option 2 (better): Tuples! And computed properties!

Tuples are a great feature of Swift because they give you the power of creating ad-hoc types. That means you can define it in-line. Sweet!

If each of your error types are going to have a code and a description, then you could have a computed info property (hopefully with a better name?). See below:

enum ErrorType {
    case teapot
    case skillet

    var info: (code: Int, description: String) {
        switch self {
        case .teapot:
            return (418, "Hear me shout!")
        case .skillet:
            return (326, "I'm big and heavy.")
        }
    }
}

Calling this would be much easier because you could use tasty, tasty dot syntax:

let errorCode = myErrorType.info.code

Solution 3

I created a way of simulating this (No different than what Marcos Crispino suggested on his answer). Far from a perfect solution but allows us to avoid those nasty switch cases for every different property we want to get.

The trick is to use a struct as the "properties/data" holder and using it as a RawValue in the enum itself.

It has a bit of duplication but it's serving me well so far. Every time you want to add a new enum case, the compiler will remind you to fill in the extra case in the rawValue getter, which should remind you to update the init? which would remind you to create the new static property on the struct.

Gist

Code to the Gist:

enum VehicleType : RawRepresentable {

    struct Vehicle : Equatable {
        let name: String
        let wheels: Int

        static func ==(l: Vehicle, r: Vehicle) -> Bool {
            return l.name == r.name && l.wheels == r.wheels
        }

        static var bike: Vehicle {
            return Vehicle(name: "Bicycle", wheels: 2)
        }

        static var car: Vehicle {
            return Vehicle(name: "Automobile", wheels: 4)
        }

        static var bus: Vehicle {
            return Vehicle(name: "Autobus", wheels: 8)
        }
    }

    typealias RawValue = Vehicle

    case car
    case bus
    case bike

    var rawValue: RawValue {
        switch self {
        case .car:
            return Vehicle.car
        case .bike:
            return Vehicle.bike
        case .bus:
            return Vehicle.bus
        }
    }

    init?(rawValue: RawValue) {
        switch rawValue {
        case Vehicle.bike:
            self = .bike
        case Vehicle.car:
            self = .car
        case Vehicle.bus:
            self = .bus
        default: return nil
        }
    }
}

VehicleType.bike.rawValue.name
VehicleType.bike.rawValue.wheels
VehicleType.car.rawValue.wheels

VehicleType(rawValue: .bike)?.rawValue.name => "Bicycle"
VehicleType(rawValue: .bike)?.rawValue.wheels => 2
VehicleType(rawValue: .car)?.rawValue.name => "Automobile"
VehicleType(rawValue: .car)?.rawValue.wheels => 4
VehicleType(rawValue: .bus)?.rawValue.name => "Autobus"
VehicleType(rawValue: .bus)?.rawValue.wheels => 8

Solution 4

No, you cannot have multiple raw values associated with an enum.

In your case, you could have the raw value to be equal to the code, and have an associated value with the description. But I think the computed properties approach is the best option here.

Solution 5

One workaround if you wanted to have many static properties for a YourError could be to import a property list; you could set the root object to a dictionary, with your enum raw value as the key for each object, allowing you to easily retrieve static structured data for the object.

This has an example of importing and using a plist: http://www.spritekitlessons.com/parsing-a-property-list-using-swift/

That might be overkill for simply an error description, for which you could just use a hardcoded static function with a switch statement for your enum values, that returns the error string you need. Simply place the static function in the same .swift file as your enum.

For instance,

static func codeForError(error : YourErrorType) -> Int {
    switch(error) {
        case .Teapot:
            return "I'm a Teapot"
        case .Teacup:
            return "I'm a Teacup"
        ...
        default:
            return "Unknown Teaware Error"
    }
}

This has the benefit (compared to the .plist solution) of better accomodating localization. However, a .plist could just contain a key used for retrieving the proper localization, instead of a error string, for this purpose.

Share:
24,498
Robert Atkins
Author by

Robert Atkins

Updated on July 25, 2022

Comments

  • Robert Atkins
    Robert Atkins almost 2 years

    I want to associate two raw values to an enum instance (imagine an enum representing error types, I want Error.Teapot to have an Int type property code with value 418, and a String property set to I'm a teapot.)

    Note the difference between raw values and associated values here—I want all Teapot instances to have a code of 418, I don't want a unique associated value for each Teapot instance.

    Is there a better way than adding computed properties to the enum that switched on self to look up the appropriate value?