¿Cómo obtener el nombre del valor de enumeración en Swift?

167

Si tengo una enumeración con Integervalores sin formato:

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

let city = City.Melbourne

¿Cómo puedo convertir un cityvalor en una cadena Melbourne? ¿Este tipo de introspección de nombre de tipo está disponible en el idioma?

Algo así (este código no funcionará):

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

Respuestas:

139

A partir de Xcode 7 beta 5 (Swift versión 2), ahora puede imprimir nombres de tipos y casos de enumeración de forma predeterminada utilizando print(_:), o Stringusar Stringla init(_:)inicialización o la sintaxis de interpolación de cadenas. Entonces, para su ejemplo:

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"

Por lo tanto, ya no es necesario definir y mantener una función conveniente que active cada caso para devolver un literal de cadena. Además, esto funciona automáticamente para cualquier enumeración, incluso si no se especifica ningún tipo de valor sin procesar.

debugPrint(_:)& String(reflecting:)se puede usar para un nombre completo:

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

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

Tenga en cuenta que puede personalizar lo que se imprime en cada uno de estos escenarios:

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)"

(No he encontrado una manera de llamar a este valor "predeterminado", por ejemplo, para imprimir "La ciudad es Melbourne" sin recurrir a una declaración de cambio. El uso \(self)en la implementación de description/ debugDescriptionprovoca una recursión infinita).


Los comentarios anteriores String's init(_:)y init(reflecting:)inicializadores describen exactamente lo que está impreso, dependiendo de qué tipo se ajusta a los reflejados a:

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 las notas de la versión para obtener información sobre este cambio.

Stuart
fuente
8
Además, si desea el valor de la cadena sin usar print(enum), puede usarString(enum)
Kametrixom
44
Captura importante, esto solo funciona para las enumeraciones Swift. Si lo etiqueta @objc para permitir el soporte de enlace en OS X, esto no funcionará.
Claus Jørgensen
11
Gran respuesta específica de Swift; sin embargo, si necesita hacer esto en una enumeración no rápida, como imprimir el CLAuthorizationStatusvalor de la enumeración (Objetivo C) dentro de la locationManager didChangeAuthorizationStatusdevolución de llamada de su delegado, deberá definir una extensión de protocolo. Por ejemplo: extension CLAuthorizationStatus: CustomStringConvertable { public var description: String { switch self { case .AuthorizedAlways: return "AuthorizedAlways" <etc> } } }- una vez que haya hecho esto, debería funcionar como cabría esperar: print ("Estado de autenticación: (\ status))".
Jeffro
3
"A partir de Xcode 7 beta 5" no tiene sentido. No es Xcode lo que define nada de esto, es el compilador Swift y las Swary Runtime Libaries. Puedo usar Xcode 9.3 pero mi código aún puede ser Swift 3 y luego no podré usar las funciones de Swift 4. Usando Xcode 9.3, este código no funciona a pesar de que Xcode 9.3 es mucho más nuevo que Xcode 7.
Mecki
8
Obtuve el inicializador 'init (_ :)' requiere que City se ajuste a 'LosslessStringConvertible' en xcode 10.2, Swift 5. ¿Cómo lo hacemos ahora?
rockgecko
73

No hay introspección en casos de enumeración en este momento. Deberá declararlos cada uno manualmente:

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

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

Si necesita que el tipo sin formato sea un Int, tendrá que hacer un cambio usted mismo:

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
fuente
2
Pregunta novata, pero ¿por qué poner get {return self.rawValue} en lugar de simplemente return self.value? Probé el último y funciona bien.
Chuck Krutsinger el
También puede omitir la get { ... }parte por brevedad si no define un setter.
iosdude
1
Gracias por la gran respuesta. En Xcode 7.3, obtengo: "Se ha cambiado el nombre de Imprimible a CustomStringConvertible". La solución es fácil: en el primer ejemplo de código anterior, cambie la primera línea a enum City : String, CustomStringConvertible {. Como parte del protocolo CSC, deberá cambiar la propiedad para que sea pública , por ejemplo:public var description : String {
Jeffro
44

En Swift-3 (probado con Xcode 8.1) puede agregar los siguientes métodos en su enumeración:

/**
 * 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) }
}

Luego puede usarlo como una llamada a método normal en su instancia de enumeración. También podría funcionar en versiones anteriores de Swift, pero no lo he probado.

En tu ejemplo:

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"

Si desea proporcionar esta funcionalidad a todas sus enumeraciones, puede convertirla en una extensión:

/**
 * 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) }
  }
}

Esto solo funciona para las enumeraciones Swift.

Matthias Voss
fuente
18

Para Objective-C enum, la única forma actualmente parece ser, por ejemplo, extender la enumeración para CustomStringConvertibleterminar con 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"
        }
    }
}

Y luego lanzando el enumcomo String:

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

El String(describing:)inicializador se puede usar para devolver el nombre de la etiqueta del caso incluso para enumeraciones con valores sin procesar de String:

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

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

Tenga en cuenta que esto no funciona si la enumeración utiliza el @objcmodificador:

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

Las interfaces Swift generadas para los tipos Objective-C a veces no incluyen el @objcmodificador. Sin embargo, esas enumeraciones se definen en Objective-C y, por lo tanto, no funcionan como anteriormente.

pkamb
fuente
7

Además del soporte de String (...) (CustomStringConvertible) para enumeraciones en Swift 2.2, también hay soporte de reflexión algo roto para ellos. Para casos enum con valores asociados, es posible obtener la etiqueta del caso enum mediante la reflexión:

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"

Sin embargo, al estar roto, quise decir que para las enumeraciones "simples", la labelpropiedad calculada basada en la reflexión anterior simplemente devuelve nil(boo-hoo).

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

La situación con la reflexión debería estar mejorando después de Swift 3, aparentemente. Sin embargo String(…), la solución por ahora es , como se sugiere en una de las otras respuestas:

print(String(City.Chelyabinsk)) // prints out Cheylabinsk
mz2
fuente
2
Esto parece funcionar en Swift 3.1 sin necesidad de hacerlo 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

Esto es muy decepcionante.

Para el caso en que necesita esos nombres (que el compilador conoce perfectamente la ortografía exacta, pero se niega a permitir el acceso, ¡gracias al equipo de Swift!), Pero no quiere o no puede hacer de String la base de su enumeración, un La alternativa detallada y engorrosa es la siguiente:

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

Puede usar lo anterior de la siguiente manera:

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

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

Y obtendrá el resultado esperado (código para la columna similar, pero no mostrado)

fetching element Title, column: Collections, row: 0

En lo anterior, hice que la descriptionpropiedad se refiera al stringmétodo, pero esto es cuestión de gustos. También tenga en cuenta que las llamadas staticvariables deben ser calificadas por el alcance por el nombre de su tipo de inclusión, ya que el compilador es demasiado amnésico y no puede recordar el contexto por sí mismo ...

El equipo Swift realmente debe ser comandado. ¡Crearon una enumeración que no puedes enumeratey que puedes usar enumerateen "Secuencias" pero no enum!

verec
fuente
Eso parece bastante largo aliento que simplemente hacer una cadena de retorno (que refleja: self) en la descripción.
Boon
4

Me topé con esta pregunta y quería compartir una forma simple de crear la función 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
fuente
3

Swift ahora tiene lo que se conoce como valor bruto asignado implícitamente . Básicamente, si no proporciona valores sin formato a cada caso y la enumeración es de tipo String, deduce que el valor sin formato del caso está en formato de cadena. Vamos, dale una oportunidad.

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

let city = City.Melbourne.rawValue

// city is "Melbourne"
NSCoder
fuente
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"
        }
    }

}

si su variable "batteryState" entonces llame:

self.batteryState.description
xevser
fuente
1

Simple pero 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
fuente