Czy w języku Swift można przekonwertować ciąg na wyliczenie?

97

Jeśli mam wyliczenie ze przypadkami a, b, c, d, czy mogę rzucić ciąg „a” jako wyliczenie?

rmaddy
źródło
3
Te „odlewy” nazywane są dosłownymi konwersjami.
Vatsal Manot

Odpowiedzi:

141

Pewnie. Wyliczenia mogą mieć wartość surową. Cytując dokumenty:

Nieprzetworzone wartości mogą być łańcuchami, znakami lub dowolnym typem liczb całkowitych lub zmiennoprzecinkowych

- Fragment od: Apple Inc. „Swift Programming Language”. iBooks. https://itun.es/us/jEUH0.l ,

Możesz więc użyć takiego kodu:

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

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

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

Uwaga: nie musisz wpisywać = "jeden" itp. Po każdym przypadku. Domyślne wartości ciągów są takie same jak nazwy przypadków, więc wywołanie .rawValuezwróci tylko ciąg

EDYTOWAĆ

Jeśli chcesz, aby wartość ciągu zawierała takie rzeczy, jak spacje, które nie są poprawne jako część wartości wielkości liter, musisz jawnie ustawić ciąg. Więc,

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

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

daje

anEnum = "jeden"

Ale jeśli chcesz case onewyświetlić „wartość 1”, musisz podać wartości ciągu:

enum StringEnum: String 
{
  case one   = "value one"
  case two   = "value two"
  case three = "value three"
}
Duncan C
źródło
Wartość surowa musi być dosłownie zamienialna. Nie możesz użyć dowolnego Hashabletypu.
Vatsal Manot
1
Ok ... zacytowałem dokumentację Apple, która zawiera listę typów wartości, których można użyć jako wyliczeń surowych wartości. Ciągi znaków, pytanie OP, są jednym z obsługiwanych typów.
Duncan C
1
Hmm, wyobraź sobie case one = "uno". Teraz, jak przeanalizować "one"wartość wyliczenia? (nie można używać
surowych plików
Może mógłbyś zainicjować nieprzetworzony ciąg znaków podczas inicjalizacji, w zależności od lokalizacji ... lub po prostu mieć różne wyliczenia dla różnych lokalizacji. W każdym razie celem wyliczenia jest wyodrębnienie podstawowego surowca, tj. Lokalizacji. Dobry projekt kodu nie przekazywałby „uno” jako parametru w dowolnym miejscu, ale polegałby na StringEnum.one
SkyWalker
5
Nie musisz pisać = "one"itp. Po każdej sprawie. Domyślne wartości ciągów są takie same jak nazwy przypadków.
emlai
38

Wszystko czego potrzebujesz to:

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

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

let 💩 = Foo(rawValue: "💩")
assert(💩 == nil)
emlai
źródło
To nie jest technicznie właściwa odpowiedź, ponieważ sprawdza wartość surową. W podanym przykładzie nie określono wartości surowej, więc jest ona niejawnie dopasowywana do nazwy sprawy, ale jeśli masz wyliczenie z wartością surową, to się zepsuje.
Mark A. Donohoe
30

W Swift 4.2 protokół CaseIterable może być używany do wyliczenia z rawValues, ale ciąg powinien pasować do etykiet wyliczeniowych wielkości liter:

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

stosowanie:

print(MyCode.withLabel("one")) // Optional(MyCode.one)
print(MyCode(rawValue: "uno"))  // Optional(MyCode.one)
djruss70
źródło
2
To świetna odpowiedź! W rzeczywistości odpowiada na to pytanie.
Matt Rundle,
3
To jedyna odpowiedź, która faktycznie działa zgodnie z zapytaniem OP, a dotyczyła nazw przypadków, a nie wartości surowych. Dobra odpowiedź!
Mark A. Donohoe
1
Chociaż to działa, jest to bardzo głupia rzecz. Nie opieraj funkcjonalności na nazwach przypadków w kodzie.
Sulthan
7
Co jeszcze ma zrobić? A co, jeśli pisze wyliczenie do bazy danych, a następnie musi je przesłać z powrotem?
Joe
16

W przypadku wyliczenia typu Int możesz to zrobić tak:

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

I użyć:

let string = "Two"
if let item = MenuItem.enumFromString(string) {
    //in this case item = 1 
    //your code
} 
Igor
źródło
2
To szalone, że nie można po prostu używać podobnej funkcji wbudowanej w język. Mogę sobie wyobrazić, że przechowujesz wartości w JSON, na przykład według nazwy wyliczenia, a następnie podczas analizowania musisz je przekonwertować. Pisanie enumFromStringmetody dla każdego używanego wyliczenia wydaje się szalone.
Peterdk
1
@Peterdk, proszę zasugerować najlepszą możliwą alternatywę. Rozwiązanie Igora faktycznie zadziałało dla mnie.
Hemang
@Hemang Działa dobrze, w porządku, ale lepszym rozwiązaniem byłoby szybkie wsparcie dla automatycznego robienia tego. Szaleństwem jest robienie tego ręcznie dla każdego wyliczenia. Ale tak, to działa.
Peterdk
@Peterdk, czy możesz dodać osobną odpowiedź na to samo? Z pewnością pomogłoby to wszystkim tutaj.
Hemang
1
Nie jest szalone, że Swift nie obsługuje go natywnie. Szalone jest to, że funkcjonalność zależy od nazwy typu. Gdy wartość się zmieni, będziesz musiał refaktoryzować i zmieniać nazwy wszystkich zastosowań. To nie jest właściwy sposób rozwiązania tego problemu.
Sulthan
2

Rozszerzam odpowiedź Duncana 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
źródło
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
    }
}
mr.boyfox
źródło
2

W przypadku wyliczenia Int i ich reprezentacji typu String deklaruję wyliczenie w następujący sposób:

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

Stosowanie:

order.orderState = OrderState.waiting.rawValue

let orderState = OrderState.init(rawValue: order.orderState)
let orderStateStr = orderState?.description ?? ""
print("orderStateStr = \(orderStateStr)")
Ammar Mujeeb
źródło
1

Riffowanie na odpowiedź djruss70, aby stworzyć wysoce uogólnione rozwiązanie:

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

Stosowanie:

enum Chassis: CaseIterable {
    case pieridae, oovidae
}

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

Uwaga: to niestety nie zadziała, jeśli wyliczenie zostanie zadeklarowane jako @objc. O ile wiem, w Swift 5.3 nie ma sposobu, aby to działało z enum @objc, z wyjątkiem rozwiązań brutalnej siły (instrukcja przełączania).

Gdyby ktoś wiedział, jak sprawić, by to zadziałało dla wyliczeń @objc, byłabym bardzo zainteresowana odpowiedzią.

aepryus
źródło