Mark A. Donohoe
Mark A. Donohoe

Reputation: 30458

Can you initialize an Enum value from the name of its case (*not* its RawValue?)

Consider this enumeration (note its type is Int)

enum MyTestEnum : Int{
    case one     = 1
    case eight   = 8
    case unknown = -1
}

You can easily initialize a version of this based on the raw value, like so...

let value = MyTestEnum(rawValue:-1)

I'm trying to find out if it can be initialized with a string representation of the case name itself (again, not the raw value, but the word after 'case') like so...

let value = MyTestEnum(caseName:"eight")

Note: I want this to work with any enum if possible, regardless of its raw value type. For instance, this one...

enum MyOtherEnum{
    case xxx
    case yyy
    case zzz
}

let value = MyOtherEnum(caseName:"xxx")

So can this be done?

Thoughts:

Upvotes: 1

Views: 7408

Answers (4)

Bretsko
Bretsko

Reputation: 918

Based on CodeBender's solution here is a nice extension for this case

extension CaseIterable {

    ///Note: case value and name can be different
    init?(caseName: String) {
        for v in Self.allCases where "\(v)" == caseName {
            self = v
            return
        }
        return nil
    }
}

Upvotes: 3

CodeBender
CodeBender

Reputation: 36660

In Swift 4.2, this is quite easy to do now using CaseIterable.

enum MyOtherEnum: CaseIterable {
    case xxx
    case yyy
    case zzz
    
    init?(caseName: String) {
        for value in MyOtherEnum.allCases where "\(value)" == caseName {
            self = value
            return
        }
        
        return nil
    }
}

enum MyTestEnum: Int, CaseIterable{
    case one     = 1
    case eight   = 8
    case unknown = -1
    
    init?(caseName: String) {
        for value in MyTestEnum.allCases where "\(value)" == caseName {
            self = value
            return
        }
        
        return nil
    }
}

What I am doing here is creating a failable initializer which iterates through all potential cases, testing to see if "\(value)" (which returns the name for that potential case) matches the caseName argument passed in to the initializer.

When a match is found, self is set and the loop ends. Otherwise, nil is returned for the call.

Below, are two working and two failing examples:

let myOtherEnum = MyOtherEnum(caseName:"xxx")
print(myOtherEnum) // MyOtherEnum.xxx

let myTestEnum = MyTestEnum(caseName:"eight")
print(myTestEnum?.rawValue) // 8

let myOtherEnumFail = MyOtherEnum(caseName:"aaa")
print(myOtherEnumFail) // nil

let myTestEnumFail = MyTestEnum(caseName:"ten")
print(myTestEnumFail) // nil

Upvotes: 12

Sua Le
Sua Le

Reputation: 137

You can go with custom initializer

extension MyTestEnum {
    public static func allValues() -> [MyTestEnum] {
        let retVal = AnySequence { () -> AnyIterator<MyTestEnum> in
            var raw = 0
            return AnyIterator {
                let current = withUnsafePointer(to: &raw) {
                    $0.withMemoryRebound(to: MyTestEnum.self, capacity: 1) { $0.pointee }
                }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }

        return [MyTestEnum](retVal)
    }

    init?(caseName: String){
        for v in MyTestEnum.allValues() {
            if "\(v)" == caseName {
                self = v
                return
            }
        }
        self = MyTestEnum.unknown
    }
}

let test = MyTestEnum(caseName: "eight")

or simple manually all your case :)

extension MyTestEnum {
 init?(caseName: String){
   switch caseName {
     case "eight": self.init(rawValue: 8)
     case "one": self.init(rawValue: 1)
     default: self.init(rawValue: -1)
   }
 }
}

let test1 = MyTestEnum(caseName: "eight") 
let test2 = MyTestEnum(rawValue: 1)

Hope this helps !!

Upvotes: 1

andyvn22
andyvn22

Reputation: 14824

It sounds like your want your enum cases to have raw Int values and raw String values. It's not strictly possible to do this, but perhaps this comes close:

enum MyTestEnum : String {
    case one
    case eight
    case unknown

    var intValue: Int {
        switch self {
        case one: return 1
        case eight: return 8
        case unknown: return -1
        }
    }
}

Now you can initialize from case names, but you can also retrieve an intValue when needed. (If needed, you could easily add a failable initializer to allow initialization from integers as well.)

Upvotes: -1

Related Questions