Como obter o nome do valor da enumeração no Swift?

167

Se eu tiver uma enumeração com Integervalores brutos :

enum City: Int {
  case Melbourne = 1, Chelyabinsk, Bursa
}

let city = City.Melbourne

Como posso converter um cityvalor em uma string Melbourne? Esse tipo de introspecção de nome de tipo está disponível no idioma?

Algo como (este código não funcionará):

println("Your city is \(city.magicFunction)")
> Your city is Melbourne
Evgenii
fonte

Respostas:

139

A partir do Xcode 7 beta 5 (Swift versão 2) você pode agora imprimir nomes de tipo e casos de enumeração por padrão usando print(_:), ou converter para Stringusar String's init(_:)sintaxe inicializador ou string de interpolação. Então, para o seu exemplo:

enum City: Int {
    case Melbourne = 1, Chelyabinsk, Bursa
}
let city = City.Melbourne

print(city)
// prints "Melbourne"

let cityName = "\(city)"   // or `let cityName = String(city)`
// cityName contains "Melbourne"

Portanto, não há mais a necessidade de definir e manter uma função de conveniência que alterne em cada caso para retornar uma string literal. Além disso, isso funciona automaticamente para qualquer enumeração, mesmo que nenhum tipo de valor bruto seja especificado.

debugPrint(_:)& String(reflecting:)pode ser usado para um nome completo:

debugPrint(city)
// prints "App.City.Melbourne" (or similar, depending on the full scope)

let cityDebugName = String(reflecting: city)
// cityDebugName contains "App.City.Melbourne"

Observe que você pode personalizar o que é impresso em cada um destes cenários:

extension City: CustomStringConvertible {
    var description: String {
        return "City \(rawValue)"
    }
}

print(city)
// prints "City 1"

extension City: CustomDebugStringConvertible {
    var debugDescription: String {
        return "City (rawValue: \(rawValue))"
    }
}

debugPrint(city)
// prints "City (rawValue: 1)"

(Não encontrei uma maneira de chamar esse valor "padrão", por exemplo, para imprimir "A cidade é Melbourne" sem recorrer a uma declaração de chave. \(self) na implementação de description/ debugDescriptioncausa uma recursão infinita.)


Os comentários acima Stringsão init(_:)&init(reflecting:) initializers descrever exatamente o que é impresso, dependendo do que o conforma tipo refletida para:

extension String {
    /// Initialize `self` with the textual representation of `instance`.
    ///
    /// * If `T` conforms to `Streamable`, the result is obtained by
    ///   calling `instance.writeTo(s)` on an empty string s.
    /// * Otherwise, if `T` conforms to `CustomStringConvertible`, the
    ///   result is `instance`'s `description`
    /// * Otherwise, if `T` conforms to `CustomDebugStringConvertible`,
    ///   the result is `instance`'s `debugDescription`
    /// * Otherwise, an unspecified result is supplied automatically by
    ///   the Swift standard library.
    ///
    /// - SeeAlso: `String.init<T>(reflecting: T)`
    public init<T>(_ instance: T)

    /// Initialize `self` with a detailed textual representation of
    /// `subject`, suitable for debugging.
    ///
    /// * If `T` conforms to `CustomDebugStringConvertible`, the result
    ///   is `subject`'s `debugDescription`.
    ///
    /// * Otherwise, if `T` conforms to `CustomStringConvertible`, the result
    ///   is `subject`'s `description`.
    ///
    /// * Otherwise, if `T` conforms to `Streamable`, the result is
    ///   obtained by calling `subject.writeTo(s)` on an empty string s.
    ///
    /// * Otherwise, an unspecified result is supplied automatically by
    ///   the Swift standard library.
    ///
    /// - SeeAlso: `String.init<T>(T)`
    public init<T>(reflecting subject: T)
}


Consulte as notas de versão para obter informações sobre essa alteração.

Stuart
fonte
8
Além disso, se você deseja que o valor da cadeia sem usar print(enum)você pode usarString(enum)
Kametrixom
44
Captura importante, isso funciona para enumerações Swift. Se você marcar @objc para permitir o suporte de ligação no OS X, isso não funcionará.
Claus Jørgensen
11
Ótima resposta específica do Swift; no entanto, se você precisar fazer isso em uma enumeração não rápida, como para imprimir o CLAuthorizationStatusvalor da enumeração (Objective C) dentro do locationManager didChangeAuthorizationStatusretorno de chamada do seu representante, precisará definir uma extensão de protocolo. Por exemplo: extension CLAuthorizationStatus: CustomStringConvertable { public var description: String { switch self { case .AuthorizedAlways: return "AuthorizedAlways" <etc> } } }- depois de fazer isso, deve funcionar como esperado: print ("Status de autenticação: (\ status))".
21416 Jeffro
3
"A partir do Xcode 7 beta 5" não faz sentido. Não é o Xcode que define nada disso, é o compilador Swift e os Swift Runtime Libaries. Posso usar o Xcode 9.3, mas meu Código ainda pode ser o Swift 3 e, em seguida, não poderei usar os recursos do Swift 4. Usando o Xcode 9.3, este código não funciona apesar Xcode 9.3 sendo muito mais recente do que Xcode 7.
Mecki
8
Eu obtive o inicializador 'init (_ :)' requer que City esteja em conformidade com 'LosslessStringConvertible' no xcode 10.2, Swift 5. Como fazemos isso agora?
Rockgecko 26/05/19
73

Não há introspecção em casos de enum no momento. Você precisará declará-los manualmente:

enum City: String, CustomStringConvertible {
    case Melbourne = "Melbourne"
    case Chelyabinsk = "Chelyabinsk"
    case Bursa = "Bursa"

    var description: String {
        get {
            return self.rawValue
        }
    }
}

Se você precisar que o tipo bruto seja um Int, precisará fazer uma alternância:

enum City: Int, CustomStringConvertible {
  case Melbourne = 1, Chelyabinsk, Bursa

  var description: String {
    get {
      switch self {
        case .Melbourne:
          return "Melbourne"
        case .Chelyabinsk:
          return "Chelyabinsk"
        case .Bursa:
          return "Bursa"
      }
    }
  }
}
drewag
fonte
2
Noob pergunta, mas por que colocar get {return self.rawValue} em vez de apenas retornar self.value? Eu tentei o último e funciona muito bem.
precisa saber é o seguinte
Você também pode omitir a get { ... }peça por questões de brevidade se não definir um setter.
iosdude
1
Obrigado pela ótima resposta. No Xcode 7.3, recebo: "Printable foi renomeado para CustomStringConvertible". A solução é fácil - no primeiro exemplo de código acima, altere a primeira linha para enum City : String, CustomStringConvertible {. Como parte do protocolo CSC, você precisará alterar a propriedade para ser pública , por exemplo:public var description : String {
Jeffro 27/04
44

No Swift-3 (testado com o Xcode 8.1), você pode adicionar os seguintes métodos na sua enumeração:

/**
 * The name of the enumeration (as written in case).
 */
var name: String {
    get { return String(describing: self) }
}

/**
 * The full name of the enumeration
 * (the name of the enum plus dot plus the name as written in case).
 */
var description: String {
    get { return String(reflecting: self) }
}

Você pode usá-lo como uma chamada de método normal em sua instância de enum. Também pode funcionar nas versões Swift anteriores, mas não testei.

No seu exemplo:

enum City: Int {
    case Melbourne = 1, Chelyabinsk, Bursa
    var name: String {
        get { return String(describing: self) }
    }
    var description: String {
        get { return String(reflecting: self) }
    }
}
let city = City.Melbourne

print(city.name)
// prints "Melbourne"

print(city.description)
// prints "City.Melbourne"

Se você deseja fornecer essa funcionalidade a todas as suas enumerações, é possível torná-la uma extensão:

/**
 * Extend all enums with a simple method to derive their names.
 */
extension RawRepresentable where RawValue: Any {
  /**
   * The name of the enumeration (as written in case).
   */
  var name: String {
    get { return String(describing: self) }
  }

  /**
   * The full name of the enumeration
   * (the name of the enum plus dot plus the name as written in case).
   */
  var description: String {
    get { return String(reflecting: self) }
  }
}

Isso funciona apenas para enumerações Swift.

Matthias Voss
fonte
18

Para Objective-C enums, atualmente, a única maneira atualmente parece ser, por exemplo, estender o enum e CustomStringConvertibleterminar com algo como:

extension UIDeviceBatteryState: CustomStringConvertible {
    public var description: String {
        switch self {
        case .Unknown:
            return "Unknown"
        case .Unplugged:
            return "Unplugged"
        case .Charging:
            return "Charging"
        case .Full:
            return "Full"
        }
    }
}

E, em seguida, lançando o enumcomo String:

String(UIDevice.currentDevice().batteryState)
Markus Rautopuro
fonte
12

O String(describing:)inicializador pode ser usado para retornar o nome do rótulo do caso, mesmo para enumerações com RawValues ​​não String.

enum Numbers: Int {
    case one = 1
    case two = 2
}

let one = String(describing: Numbers.one) // "one"
let two = String(describing: Numbers.two) // "two"

Observe que isso não funciona se o enum usa o@objc modificador:

https://forums.swift.org/t/why-is-an-enum-returning-enumname-rather-than-caselabel-for-string-describing/27327

As interfaces Swift geradas para tipos de Objective-C às vezes não incluem o @objcmodificador. No entanto, esses Enums são definidos no Objective-C e, portanto, não funcionam como acima.

pkamb
fonte
7

Além do suporte a String (…) (CustomStringConvertible) para enumerações no Swift 2.2, também há um suporte de reflexão um pouco quebrado para elas. Para casos enum com valores associados, é possível obter o rótulo do caso enum usando reflexão:

enum City {
    case Melbourne(String)
    case Chelyabinsk
    case Bursa

    var label:String? {
        let mirror = Mirror(reflecting: self)
        return mirror.children.first?.label
    }
}

print(City.Melbourne("Foobar").label) // prints out "Melbourne"

Por estar quebrado, no entanto, quis dizer que, para enums "simples", a labelpropriedade computada baseada em reflexão acima retorna nil(boo-hoo).

print(City.Chelyabinsk.label) // prints out nil

A situação com a reflexão deve melhorar depois de Swift 3, aparentemente. A solução por enquanto é String(…), como sugerido em uma das outras respostas:

print(String(City.Chelyabinsk)) // prints out Cheylabinsk
mz2
fonte
2
Isso parece funcionar no Swift 3.1 sem a necessidade de torná-lo opcional:var label:String { let mirror = Mirror(reflecting: self); if let label = mirror.children.first?.label { return label } else { return String(describing:self) } }
David James
5

Isso é tão decepcionante.

Para o caso em que você precisa desses nomes (que o compilador conhece perfeitamente a ortografia exata, mas se recusa a permitir o acesso - obrigado equipe Swift !! -), mas não deseja ou não pode fazer de String a base de sua enumeração, alternativa detalhada e complicada é a seguinte:

enum ViewType : Int, Printable {

    case    Title
    case    Buttons
    case    View

    static let all = [Title, Buttons, View]
    static let strings = ["Title", "Buttons", "View"]

    func string() -> String {
        return ViewType.strings[self.rawValue]
    }

    var description:String {
        get {
            return string()
        }
    }
}

Você pode usar o acima, da seguinte maneira:

let elementType = ViewType.Title
let column = Column.Collections
let row = 0

println("fetching element \(elementType), column: \(column.string()), row: \(row)")

E você obterá o resultado esperado (código da coluna semelhante, mas não mostrado)

fetching element Title, column: Collections, row: 0

No exposto, fiz a descriptionpropriedade se referir novamente ao stringmétodo, mas isso é uma questão de gosto. Observe também que os chamadosstatic variáveis ​​precisam ser qualificadas pelo escopo pelo nome de seu tipo de anexo, pois o compilador é muito amnésico e não pode recuperar o contexto por si só ...

A equipe Swift deve realmente ser comandada. Eles criaram um enum que você não pode enumeratee o que pode usar enumeratesão "Sequências", mas não enum!

verec
fonte
Isso parece bastante longo do que apenas retornar String (refletindo: self) na descrição.
Boon
4

Eu me deparei com essa pergunta e queria compartilhar uma maneira simples de criar a função mágica mencionada

enum City: Int {
  case Melbourne = 1, Chelyabinsk, Bursa

    func magicFunction() -> String {
        return "\(self)"
    }
}

let city = City.Melbourne
city.magicFunction() //prints Melbourne
Sev
fonte
3

Agora, o Swift tem o que é conhecido como Valor Bruto Atribuído Implicitamente . Basicamente, se você não fornecer valores brutos para cada caso e a enumeração for do tipo String, deduzirá que o valor bruto do caso está no formato de string. Vá em frente, tente.

enum City: String {
  case Melbourne, Chelyabinsk, Bursa
}

let city = City.Melbourne.rawValue

// city is "Melbourne"
NSCoder
fonte
3

Para Swift:

extension UIDeviceBatteryState: CustomStringConvertible {

    public var description: String {
        switch self {
        case .unknown:
            return "unknown"
        case .unplugged:
            return "unplugged"
        case .charging:
            return "charging"
        case .full:
            return "full"
        }
    }

}

se sua variável "batteryState", ligue para:

self.batteryState.description
xevser
fonte
1

Simples, mas funciona ...

enum ViewType : Int {
    case    Title
    case    Buttons
    case    View
}

func printEnumValue(enum: ViewType) {

    switch enum {
    case .Title: println("ViewType.Title")
    case .Buttons: println("ViewType.Buttons")
    case .View: println("ViewType.View")
    }
}
Jimbo Jones
fonte