In Swift, è possibile convertire una stringa in un'enumerazione?


93

Se ho un enum con i casi a, b, c, d è possibile per me lanciare la stringa "a" come enum?


3
Questi 'cast' sono chiamati conversioni letterali.
Vatsal Manot

Risposte:


136

Sicuro. Le enumerazioni possono avere un valore grezzo. Per citare i documenti:

I valori non elaborati possono essere stringhe, caratteri o qualsiasi tipo di numero intero o in virgola mobile

- Estratto da: Apple Inc. "The Swift Programming Language". iBooks. https://itun.es/us/jEUH0.l ,

Quindi puoi usare un codice come questo:

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

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

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

Nota: non è necessario scrivere = "uno" ecc. Dopo ogni caso. I valori di stringa predefiniti sono gli stessi dei nomi dei casi, quindi la chiamata .rawValuerestituirà solo una stringa

MODIFICARE

Se hai bisogno che il valore della stringa contenga cose come spazi che non sono validi come parte di un valore case, allora devi impostare esplicitamente la stringa. Così,

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

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

anEnum = "uno"

Ma se vuoi case onevisualizzare il "valore uno" dovrai fornire i valori della stringa:

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

Il valore grezzo deve essere convertibile letterale. Non puoi usare qualsiasi Hashabletipo.
Vatsal Manot

1
Ok ... ho citato la documentazione di Apple, che elenca i tipi di valori che possono essere utilizzati come valori grezzi enum. Le stringhe, la domanda dell'OP, sono uno dei tipi supportati.
Duncan C

1
Hmm, immagina case one = "uno". Ora, come analizzare il "one"valore enum? (non è possibile utilizzare raw, poiché vengono utilizzati per la localizzazione)
Agent_L

Forse potresti inizializzare la stringa grezza durante l'inizializzazione a seconda della localizzazione ... o semplicemente avere un'enumerazione diversa ciascuna per una diversa localizzazione. In ogni caso, l'intero scopo dell'avere un enum è astrarre il grezzo sottostante, ovvero la localizzazione. Un buon progetto di codice non passerebbe "uno" come parametro da nessuna parte, ma basandosi su StringEnum.one
SkyWalker

5
Non è necessario scrivere = "one"ecc. Dopo ogni caso. I valori di stringa predefiniti sono gli stessi dei nomi dei casi.
emlai

38

Tutto ciò che serve è:

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

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

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

Questa non è tecnicamente la risposta giusta in quanto controlla il valore grezzo. Nell'esempio qui dato, non è specificato alcun valore grezzo, quindi è implicitamente abbinato al nome del caso, ma se si dispone di un'enumerazione con un valore grezzo, questo si interrompe.
Mark A. Donohoe

26

In Swift 4.2, il protocollo CaseIterable può essere utilizzato per un'enumerazione con rawValues, ma la stringa deve corrispondere alle etichette enum case:

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

utilizzo:

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

2
Questa è un'ottima risposta! In realtà affronta la domanda.
Matt Rundle

3
Questa è l'unica risposta che funziona effettivamente come richiesto dall'OP, che riguardava i nomi dei casi non i valori grezzi. Buona risposta!
Mark A. Donohoe

1
Anche se funziona, è una cosa molto sciocca da fare. Non basare la funzionalità sui nomi dei casi nel codice.
Sulthan

7
Cos'altro dovrebbe fare? E se scrive un'enumerazione in un database e poi ha bisogno di eseguirne il cast?
Joe,

15

Nel caso in cui con un enum con tipo Int puoi farlo così:

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

E usa:

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

1
È pazzesco che non puoi semplicemente usare funzionalità simili incorporate nella lingua. Posso immaginare che memorizzi i valori in JSON, ad esempio con il nome dell'enumerazione, e quindi durante l'analisi è necessario riconvertirli. Scrivere un enumFromStringmetodo per ogni enum che usi sembra folle.
Peterdk

1
@Peterdk, suggerisci la migliore alternativa possibile. La soluzione di Igor in realtà ha funzionato per me.
Hemang

@Hemang Funziona bene, va bene, ma una soluzione migliore sarebbe il supporto Swift per farlo automaticamente. È pazzesco farlo manualmente per ogni enum. Ma sì, funziona.
Peterdk

@ Peterdk, puoi aggiungere una risposta separata per lo stesso? Sicuramente aiuterebbe tutti qui.
Hemang

1
Non è pazzesco che Swift non lo supporti in modo nativo. La cosa pazzesca è che la funzionalità si basa sul nome di un tipo. Quando il valore cambia, sarà necessario eseguire il refactoring e rinominare tutti gli utilizzi. Questo non è il modo corretto per risolvere questo problema.
Sulthan

2

Estendendo la risposta di 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)
    }
}

2

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

1

Per Int enum e la loro rappresentazione String, dichiaro enum come segue:

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

Utilizzo:

order.orderState = OrderState.waiting.rawValue

let orderState = OrderState.init(rawValue: order.orderState)
let orderStateStr = orderState?.description ?? ""
print("orderStateStr = \(orderStateStr)")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.