Come ottenere il nome del valore di enumerazione in Swift?


167

Se ho un'enumerazione con Integervalori non elaborati :

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

let city = City.Melbourne

Come posso convertire un cityvalore in una stringa Melbourne? Questo tipo di introspezione di un nome tipo è disponibile nella lingua?

Qualcosa di simile (questo codice non funzionerà):

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

Risposte:


139

A partire da Xcode 7 beta 5 (Swift versione 2) è ora possibile stampare nomi di tipi ed enum case per impostazione predefinita utilizzando print(_:)o convertire Stringutilizzando Stringl' init(_:)inizializzatore o la sintassi di interpolazione delle stringhe. Quindi, per il tuo esempio:

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"

Quindi non è più necessario definire e mantenere una funzione di convenienza che attiva ciascun caso per restituire una stringa letterale. Inoltre, questo funziona automaticamente per qualsiasi enum, anche se non viene specificato alcun tipo di valore non elaborato.

debugPrint(_:)e String(reflecting:)può essere utilizzato per un 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"

Si noti che è possibile personalizzare ciò che viene stampato in ciascuno di questi scenari:

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

(Non ho trovato il modo di chiamare questo valore "predefinito", ad esempio, per stampare "La città è Melbourne" senza ricorrere a un'istruzione switch. L'uso \(self)nell'implementazione di description/ debugDescriptionprovoca una ricorsione infinita.)


Le osservazioni di cui sopra String's init(_:)& init(reflecting:)initializers descrivono esattamente ciò che viene stampato, a seconda di ciò che il tipo soddisfa riflesse 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)
}


Vedi le note di rilascio per informazioni su questa modifica.


8
Inoltre, se vuoi il valore di stringa senza usare print(enum)puoi usareString(enum)
Kametrixom

44
Cattura importante, questo funziona solo per enumerazioni Swift. Se lo taggate @objc per consentire il supporto dell'associazione su OS X, questo non funzionerà.
Claus Jørgensen,

11
Ottima risposta specifica per Swift; tuttavia, se è necessario eseguire questa operazione su un enum non rapido, ad esempio per stampare il CLAuthorizationStatusvalore dell'enum (Obiettivo C) all'interno del locationManager didChangeAuthorizationStatuscallback del delegato, è necessario definire un'estensione del protocollo. Ad esempio: extension CLAuthorizationStatus: CustomStringConvertable { public var description: String { switch self { case .AuthorizedAlways: return "AuthorizedAlways" <etc> } } }- una volta fatto, dovrebbe funzionare come previsto: print ("Auth status: (\ status))".
Jeffro,

3
"A partire da Xcode 7 beta 5" non ha senso. Non è Xcode che definisce nulla di tutto ciò, è il compilatore Swift e le librerie Swift Runtime. Posso usare Xcode 9.3 ma il mio codice può ancora essere Swift 3 e quindi non sarò in grado di utilizzare le funzionalità di Swift 4. Utilizzando Xcode 9.3, questo codice non funziona nonostante Xcode 9.3 sia molto più recente di Xcode 7.
Mecki

8
Ho l'inizializzatore 'init (_ :)' richiede che City sia conforme a 'LosslessStringConvertible' su xcode 10.2, Swift 5. Come lo facciamo adesso?
rockgecko,

73

Al momento non ci sono introspezioni sui casi di enum. Dovrai dichiararli ciascuno manualmente:

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

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

Se hai bisogno che il tipo raw sia un Int, dovrai fare un cambio da solo:

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

2
Noob domanda, ma perché mettere get {return self.rawValue} invece di restituire self.value? Ho provato quest'ultimo e funziona benissimo.
Chuck Krutsinger

Puoi anche omettere la get { ... }parte per brevità se non definisci un setter.
iosdude,

1
Grazie per la magnifica risposta. In Xcode 7.3, ottengo: "Stampabile è stato rinominato in CustomStringConvertible". La soluzione è semplice: nel primo esempio di codice sopra, cambia la prima riga in enum City : String, CustomStringConvertible {. Come parte del protocollo CSC, dovrai quindi modificare la proprietà in modo che sia pubblica , ad esempio:public var description : String {
Jeffro

44

In Swift-3 (testato con Xcode 8.1) puoi aggiungere i seguenti metodi nel tuo enum:

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

Puoi quindi usarlo come una normale chiamata di metodo sulla tua istanza enum. Potrebbe funzionare anche nelle precedenti versioni di Swift, ma non l'ho provato.

Nel tuo esempio:

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 vuoi fornire questa funzionalità a tutti i tuoi enum, puoi renderlo un'estensione:

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

Questo funziona solo per enumerazioni Swift.


18

Per Objective-C enums l'unico modo attualmente sembra essere, ad esempio, di estendere l'enum CustomStringConvertiblefinendo con qualcosa del tipo:

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 poi lanciare il enumas String:

String(UIDevice.currentDevice().batteryState)

12

L' String(describing:)inizializzatore può essere utilizzato per restituire il nome dell'etichetta del caso anche per gli enum con valori Raw non String:

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

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

Nota che questo non funziona se l'enum usa il @objcmodificatore:

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

Le interfacce Swift generate per i tipi Objective-C a volte non includono il @objcmodificatore. Tali Enum sono tuttavia definiti in Objective-C, e quindi non funzionano come sopra.


7

Oltre al supporto String (...) (CustomStringConvertible) per gli enum in Swift 2.2, c'è anche un supporto di riflessione in qualche modo rotto per loro. Per i casi enum con valori associati è possibile ottenere l'etichetta del caso enum usando reflection:

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"

Per essere rotto, intendevo tuttavia che per gli enum "semplici", la labelproprietà calcolata sopra basata sulla riflessione ritorna nil(boo-hoo).

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

A quanto pare, la situazione con la riflessione dovrebbe migliorare dopo Swift 3. La soluzione per ora è String(…), come suggerito in una delle altre risposte:

print(String(City.Chelyabinsk)) // prints out Cheylabinsk

2
Questo sembra funzionare su Swift 3.1 senza che sia necessario renderlo facoltativo: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

Questo è così deludente.

Nel caso in cui tu abbia bisogno di quei nomi (di cui il compilatore conosce perfettamente l'ortografia esatta, ma rifiuta di consentire l'accesso - grazie a Swift team !! -) ma non vuoi o non puoi rendere String la base del tuo enum, un L'alternativa dettagliata e ingombrante è la seguente:

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

È possibile utilizzare quanto sopra come segue:

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

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

E otterrai il risultato atteso (codice per la colonna simile, ma non mostrato)

fetching element Title, column: Collections, row: 0

In quanto sopra, ho fatto in modo che la descriptionproprietà rimandasse al stringmetodo, ma è una questione di gusti. Si noti inoltre che le cosiddette staticvariabili devono essere qualificate nell'ambito dal nome del loro tipo che lo racchiude, poiché il compilatore è troppo amnesico e non è in grado di richiamare il contesto da solo ...

Il team Swift deve essere veramente comandato. Hanno creato un enum che non puoi enumeratee su cui puoi usare enumeratesono "Sequenze" ma non enum!


Sembra piuttosto a lungo piuttosto che semplicemente restituire String (riflettendo: sé) nella descrizione.
Boon

4

Mi sono imbattuto in questa domanda e volevo condividere un modo semplice per creare la funzione magica menzionata

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

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

let city = City.Melbourne
city.magicFunction() //prints Melbourne

3

Swift ora ha ciò che è noto come valore grezzo assegnato in modo implicito . Fondamentalmente se non si danno valori grezzi a ciascun caso e l'enum è di tipo String, si deduce che il valore grezzo del caso sia esso stesso in formato stringa. Su, provaci.

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

let city = City.Melbourne.rawValue

// city is "Melbourne"

3

Per 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 la tua variabile "batteryState" chiama:

self.batteryState.description

1

Semplice ma funziona ...

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