En Swift, ¿es posible convertir una cadena en una enumeración?

93

Si tengo una enumeración con los casos a, b, c, d, ¿puedo convertir la cadena "a" como enumeración?

rmaddy
fuente
3
Estos 'lanzamientos' se denominan conversiones literales.
Vatsal Manot

Respuestas:

136

Por supuesto. Las enumeraciones pueden tener un valor bruto. Para citar los documentos:

Los valores brutos pueden ser cadenas, caracteres o cualquiera de los tipos de números enteros o de punto flotante

- Extracto de: Apple Inc. "El lenguaje de programación Swift". iBooks. https://itun.es/us/jEUH0.l ,

Entonces puedes usar un código como este:

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

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

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

Nota: No es necesario escribir = "uno", etc. después de cada caso. Los valores de cadena predeterminados son los mismos que los nombres de los casos, por lo que la llamada .rawValuesolo devolverá una cadena

EDITAR

Si necesita que el valor de la cadena contenga elementos como espacios que no son válidos como parte de un valor de caso, entonces debe establecer explícitamente la cadena. Entonces,

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

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

da

anEnum = "uno"

Pero si desea case onemostrar el "valor uno", deberá proporcionar los valores de cadena:

enum StringEnum: String 
{
  case one   = "value one"
  case two   = "value two"
  case three = "value three"
}
Duncan C
fuente
El valor bruto debe ser literalmente convertible. No puedes usar cualquier Hashabletipo.
Vatsal Manot
1
Ok ... Cité los documentos de Apple, que enumeran los tipos de valores que se pueden usar como valores sin procesar de enumeración. Las cadenas, la pregunta del OP, son uno de los tipos admitidos.
Duncan C
1
Hmm, imagina case one = "uno". Ahora, ¿cómo analizar el "one"valor de enumeración? (no se pueden usar raws, ya que se usan para la localización)
Agent_L
Tal vez podría inicializar la cadena sin procesar después de la inicialización dependiendo de la localización ... o simplemente tener una enumeración diferente cada una para una localización diferente. En cualquier caso, el propósito de tener una enumeración es abstraer el crudo subyacente, es decir, la localización. Un buen diseño de código no sería pasar "uno" como parámetro en ningún lugar, sino confiar en StringEnum.one
SkyWalker
5
No es necesario escribir, = "one"etc. después de cada caso. Los valores de cadena predeterminados son los mismos que los nombres de los casos.
emlai
38

Todo lo que necesitas es:

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

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

let 💩 = Foo(rawValue: "💩")
assert(💩 == nil)
emlai
fuente
Esta no es técnicamente la respuesta correcta, ya que verifica el valor bruto. En el ejemplo que se muestra aquí, no se especifica ningún valor sin procesar, por lo que se corresponde implícitamente con el nombre del caso, pero si tiene una enumeración con un valor sin procesar, esto se rompe.
Mark A. Donohoe
26

En Swift 4.2, el protocolo CaseIterable se puede usar para una enumeración con rawValues, pero la cadena debe coincidir con las etiquetas de caso de enumeración:

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 }
    }
}

uso:

print(MyCode.withLabel("one")) // Optional(MyCode.one)
print(MyCode(rawValue: "uno"))  // Optional(MyCode.one)
djruss70
fuente
2
¡Esta es una respuesta genial! De hecho, aborda la cuestión.
Matt Rundle
3
Esta es la única respuesta que realmente funciona como pidió el OP, que se refería a nombres de casos, no valores sin procesar. ¡Buena respuesta!
Mark A. Donohoe
1
Si bien esto funciona, es una tontería. No base la funcionalidad en nombres de casos en código.
Sulthan
7
¿Qué más se supone que debe hacer? ¿Qué pasa si está escribiendo una enumeración en una base de datos y luego necesita devolverla?
Joe
15

En caso de que tenga una enumeración con tipo Int, puede hacerlo así:

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
    }
}

Y use:

let string = "Two"
if let item = MenuItem.enumFromString(string) {
    //in this case item = 1 
    //your code
} 
Igor
fuente
1
Es una locura que no pueda usar una funcionalidad similar incorporada en el lenguaje. Puedo imaginar que almacena valores en JSON, por ejemplo, por el nombre de enumeración, y luego, al analizar, debe convertirlos nuevamente. Escribir un enumFromStringmétodo para cada enumeración que usa parece una locura.
Peterdk
1
@Peterdk, sugiera la mejor alternativa posible. La solución de Igor realmente funcionó para mí.
Hemang
@Hemang Funciona bien, está bien, pero una mejor solución sería la compatibilidad con Swift para hacer esto automáticamente. Es una locura hacer esto manualmente para cada enumeración. Pero sí, esto funciona.
Peterdk
@Peterdk, ¿puede agregar una respuesta separada para lo mismo? Seguramente ayudaría a todos aquí.
Hemang
1
No es una locura que Swift no lo admita de forma nativa. Lo loco es que la funcionalidad se basa en el nombre de un tipo. Cuando el valor cambie, tendrá que refactorizar y renombrar todos los usos. Esta no es la forma correcta de solucionar este problema.
Sulthan
2

Ampliando la respuesta de Duncan C

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)
    }
}
gujci
fuente
2

Rápido 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
    }
}
mr.boyfox
fuente
1

Para Int enum y su representación de cadena, declaro enum de la siguiente manera:

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
        }
    }
}

Uso:

order.orderState = OrderState.waiting.rawValue

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