128

If I have an enum with the cases a,b,c,d is it possible for me to cast the string "a" as the enum?

1
  • 4
    These 'casts' are called literal conversions. Commented May 4, 2015 at 4:02

10 Answers 10

191

Sure. Enums can have a raw value. To quote the docs:

Raw values can be strings, characters, or any of the integer or floating-point number types

— Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l,

So you can use code like this:

enum StringEnum: String 
{
    case one   = "value one"
    case two   = "value two"
    case three = "value three"
}

let anEnum = StringEnum(rawValue: "value one")!

print("anEnum = \"\(anEnum.rawValue)\"")

Note: You don't need to write = "one" etc. after each case. The default string values are the same as the case names so calling .rawValue will just return a string

EDIT

If you need the string value to contain things like spaces that are not valid as part of a case value then you need to explicitly set the string. So,

enum StringEnum: String 
{
  case one
  case two
  case three
}

let anEnum = StringEnum.one
print("anEnum = \"\(anEnum)\"")

gives

anEnum = "one"

But if you want case one to display "value one" you will need to provide the string values:

enum StringEnum: String 
{
  case one   = "value one"
  case two   = "value two"
  case three = "value three"
}
Sign up to request clarification or add additional context in comments.

10 Comments

The raw value must be literal convertible. You can't use just any Hashable type.
Ok... I quoted the Apple docs, which lists the types of values that can be used as enum raw values. Strings, the OP's question, are one of the supported types.
Hmm, imagine case one = "uno". Now, how to parse "one" to enum value? (can't use raws, as they're used for localisation)
Maybe you could initialize the raw String upon initialization depending on the localization ... or simply have different enum each for a different localization. In any case the whole purpose of having an enum is to abstract away the underlying raw i.e. the localization. A good code design would not be passing "uno" as parameter anywhere but relying on StringEnum.one
You don't need to write = "one" etc. after each case. The default string values are the same as the case names.
|
50

In Swift 4.2, the CaseIterable protocol can be used for an enum with rawValues, but the string should match against the enum case labels:

enum MyCode : String, CaseIterable {

    case one   = "uno"
    case two   = "dos"
    case three = "tres"

    static func withLabel(_ label: String) -> MyCode? {
        return self.allCases.first{ "\($0)" == label }
    }
}

usage:

print(MyCode.withLabel("one")) // Optional(MyCode.one)
print(MyCode(rawValue: "uno"))  // Optional(MyCode.one)

4 Comments

This is a great answer! It actually addresses the question.
This is the only answer that actually works as the OP asked, which was about case names not raw values. Good answer!
While this works, its a very silly thing to do. Please dont base functionality on names of cases in code.
What else is he supposed to do? What if he's writing an enum to a database and then needs to cast it back?
45

All you need is:

enum Foo: String {
   case a, b, c, d
}

let a = Foo(rawValue: "a")
assert(a == Foo.a)

let 💩 = Foo(rawValue: "💩")
assert(💩 == nil)

1 Comment

This isn't technically the right answer as this checks the raw value. In the example here as given, there is no raw value specified, so it's implicitly matched to the case name, but if you have an enum with a raw value, this breaks.
21

In case with an enum with Int type you can do it so:

enum MenuItem: Int {
    case One = 0, Two, Three, Four, Five //... as much as needs

    static func enumFromString(string:String) -> MenuItem? {
        var i = 0
        while let item = MenuItem(rawValue: i) {
            if String(item) == string { return item }
            i += 1
        }
        return nil
    }
}

And use:

let string = "Two"
if let item = MenuItem.enumFromString(string) {
    //in this case item = 1 
    //your code
} 

5 Comments

It's crazy that you can't just use similar functionality builtin into the language. I can imagine you store values in JSON for example by the enum name, and then on parsing need to convert them back. Writing a enumFromString method for each enum you use seems crazy.
@Peterdk, please suggest the best possible alternative. Igor solution actually just worked for me.
@Hemang It works ok, alright, but a better solution would be Swift support for automatically doing this. It's crazy to do this manually for each enum. But yes, this works.
@Peterdk, can you please add a separate answer for the same? It would surely help everyone out here.
Its not crazy that Swift does not support it natively. The crazy thing is that the functionality rely on the name of a type. When the value changes, you will have to refactor and rename all usages. This is not the correct way to solve this.
10

Riffing on djruss70's answer to create highly generalized solution:

extension CaseIterable {
    static func from(string: String) -> Self? { Self.allCases.first { string == "\($0)" } }
    func toString() -> String { "\(self)" }
}

Usage:

enum Chassis: CaseIterable {
    case pieridae, oovidae
}

let chassis: Chassis = Chassis.from(string: "oovidae")!
let string: String = chassis.toString()

Note: this will unfortunately not work if the enum is declared @objc. As far as I know as of Swift 5.3 there is no way to get this to work with @objc enum's except brute force solutions (a switch statement).

If someone happens to know of a way to make this work for @objc enums, I'd be very interested in the answer.

Comments

3

Swift 4.2:

public enum PaymentPlatform: String, CaseIterable {
    case visa = "Visa card"
    case masterCard = "Master card"
    case cod = "Cod"

    var nameEnum: String {
        return Mirror(reflecting: self).children.first?.label ?? String(describing: self)
    }

    func byName(name: String) -> PaymentPlatform {
        return PaymentPlatform.allCases.first(where: {$0.nameEnum.elementsEqual(name)}) ?? .cod
    }
}

Comments

3

I found the other answers make this way more complicated then it needs to be. Here is a quick and concise example.

enum Gender: String {
    case male, female, unspecified
}

Simple enum, note that I added ": String" to the enum itself to declare the type as string.

Now all you have to do is:

let example: Gender? = Gender(rawValue: "male")

And thats it, example is now an enum of type Gender with the value of .male

Note that of course nil is returned if there's no such value.

let example: Gender? = Gender(rawValue: "xxx")

example is nil.

There is literally nothing else you need to do in Swift 4+.

Comments

2

Extending Duncan C's answer

extension StringEnum: StringLiteralConvertible {

    init(stringLiteral value: String){
        self.init(rawValue: value)!
    }

    init(extendedGraphemeClusterLiteral value: String) {
        self.init(stringLiteral: value)
    }

    init(unicodeScalarLiteral value: String) {
        self.init(stringLiteral: value)
    }
}

Comments

2

For Int enum and their String representation, I declare enum as follows:

enum OrderState: Int16, CustomStringConvertible {

    case waiting = 1
    case inKitchen = 2
    case ready = 3

    var description: String {
        switch self {
        case .waiting:
            return "Waiting"
        case .inKitchen:
            return "InKitchen"
        case .ready:
            return "Ready"
        }
    }

    static func initialize(stringValue: String)-> OrderState? {
        switch stringValue {
        case OrderState.waiting.description:
            return OrderState.waiting
        case OrderState.inKitchen.description:
            return OrderState.inKitchen
        case OrderState.ready.description:
            return OrderState.ready

        default:
            return nil
        }
    }
}

Usage:

order.orderState = OrderState.waiting.rawValue

let orderState = OrderState.init(rawValue: order.orderState)
let orderStateStr = orderState?.description ?? ""
print("orderStateStr = \(orderStateStr)")

Comments

0

I used this:

public enum Currency: CaseIterable, Codable {
    case AFN = 971 // Afghani (minor=2)
    case DZD = 012 // Algerian Dinar (minor=2)

...

    private static var cachedLookup: [String: Currency] = [:]
    
    init?(string: String) {
        if Self.cachedLookup.isEmpty {
            Self.cachedLookup = Dictionary(uniqueKeysWithValues: Self.allCases.map { ("\($0)", $0) })
        }
        
        if let currency = Self.cachedLookup[string] {
            self = currency
            return
        } else {
            return nil
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.