В Swift можно ли преобразовать строку в перечисление?


96

Если у меня есть перечисление с вариантами a, b, c, d, могу ли я указать строку «a» в качестве перечисления?


3
Эти «преобразования» называются буквальными преобразованиями.
Ватсал Манот

Ответы:


141

Конечно. Перечисления могут иметь необработанное значение. Чтобы процитировать документы:

Необработанные значения могут быть строками, символами или любыми целыми числами или числами с плавающей запятой.

- Отрывок из: Apple Inc. «Быстрый язык программирования». iBooks. https://itun.es/us/jEUH0.l ,

Итак, вы можете использовать такой код:

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

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

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

Примечание: вам не нужно писать = "one" и т. Д. После каждого случая. Строковые значения по умолчанию совпадают с именами случаев, поэтому вызов .rawValueпросто вернет строку

РЕДАКТИРОВАТЬ

Если вам нужно, чтобы строковое значение содержало такие вещи, как пробелы, которые недопустимы как часть значения case, вам необходимо явно установить строку. Так,

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

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

дает

anEnum = "один"

Но если вы хотите case oneотобразить «значение один», вам нужно будет указать строковые значения:

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

Необработанное значение должно быть буквально конвертируемым. Вы не можете использовать любой Hashableтип.
Ватсал Манот

1
Хорошо ... Я процитировал документы Apple, в которых перечислены типы значений, которые могут использоваться в качестве исходных значений enum. Строки, вопрос OP, являются одним из поддерживаемых типов.
Duncan C

1
Хм, представьте case one = "uno". Теперь, как разобрать "one"значение enum? (нельзя использовать
сырые файлы

Возможно, вы могли бы инициализировать необработанную строку при инициализации в зависимости от локализации ... или просто иметь разные перечисления для каждой локализации. В любом случае вся цель перечисления состоит в том, чтобы абстрагироваться от исходных данных, то есть от локализации. Хороший дизайн кода не будет передавать "uno" в качестве параметра где-либо, кроме как полагаться на StringEnum.one
SkyWalker

5
= "one"После каждого случая писать и т. Д. Не нужно . Строковые значения по умолчанию совпадают с именами случаев.
emlai 05

38

Все, что тебе нужно:

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

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

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

Технически это неправильный ответ, поскольку проверяется необработанное значение. В приведенном здесь примере не указано исходное значение, поэтому оно неявно сопоставляется с именем дела, но если у вас есть перечисление с необработанным значением, это прерывается.
Марк А. Донохо

30

В Swift 4.2 протокол CaseIterable можно использовать для перечисления с rawValues, но строка должна соответствовать меткам перечисления 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 }
    }
}

Применение:

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

2
Это отличный ответ! Фактически это решает вопрос.
Мэтт Рандл

3
Это единственный ответ, который действительно работает так, как просил OP, который касался имен случаев, а не необработанных значений. Хороший ответ!
Марк А. Донохо

1
Хотя это работает, это очень глупая вещь. Пожалуйста, не основывайте функциональные возможности на именах случаев в коде.
Sulthan

7
Что ему еще делать? Что, если он пишет перечисление в базу данных, а затем ему нужно вернуть его?
Джо

16

В случае с перечислением с типом Int вы можете сделать это так:

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

И используйте:

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

2
Это безумие, что вы не можете просто использовать аналогичные функции, встроенные в язык. Я могу представить, что вы храните значения в JSON, например, по имени перечисления, а затем при синтаксическом анализе необходимо преобразовать их обратно. Написание enumFromStringметода для каждого используемого перечисления кажется безумием.
Peterdk 05

1
@Peterdk, пожалуйста, предложите лучшую альтернативу. Решение Игоря действительно сработало для меня.
Hemang

@Hemang Он работает нормально, но лучшим решением будет поддержка Swift для автоматического выполнения этого. Безумие делать это вручную для каждого перечисления. Но да, это работает.
Peterdk

@Peterdk, не могли бы вы добавить отдельный ответ на то же самое? Это наверняка поможет всем здесь.
Hemang

1
Неудивительно, что Swift не поддерживает его изначально. Самое безумное то, что функциональность зависит от имени типа. Когда значение изменится, вам придется провести рефакторинг и переименовать все использования. Это неправильный способ решить эту проблему.
Sulthan

2

Расширение ответа Дункана 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
    }
}

2

Для Int enum и их строкового представления я объявляю enum следующим образом:

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

Применение:

order.orderState = OrderState.waiting.rawValue

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

0

Riffing на ответ djruss70 для создания обобщенного решения:

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

Применение:

enum Chassis: CaseIterable {
    case pieridae, oovidae
}

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

Примечание: это, к сожалению, не сработает, если перечисление объявлено @objc. Насколько я знаю, начиная с Swift 5.3, нет способа заставить это работать с перечислением @objc, кроме решений грубой силы (оператор switch).

Если кто-то знает, как сделать эту работу для перечислений @objc, мне будет очень интересен ответ.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.