Wie erhalte ich den Namen des Aufzählungswerts in Swift?

167

Wenn ich eine Aufzählung mit Rohwerten habe Integer:

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

let city = City.Melbourne

Wie kann ich einen cityWert in einen String konvertieren Melbourne? Ist diese Art der Selbstbeobachtung von Typnamen in der Sprache verfügbar?

So etwas wie (dieser Code funktioniert nicht):

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

Antworten:

139

Ab Xcode 7 Beta 5 (Swift Version 2) können Sie jetzt standardmäßig Typnamen und Aufzählungsfälle mit der Initialisierungs- oder Zeichenfolgeninterpolationssyntax drucken print(_:)oder in diese konvertieren . Also für Ihr Beispiel:StringStringinit(_:)

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"

Es ist also nicht mehr erforderlich, eine Komfortfunktion zu definieren und zu verwalten, die jeden Fall einschaltet, um ein Zeichenfolgenliteral zurückzugeben. Darüber hinaus funktioniert dies automatisch für jede Aufzählung, auch wenn kein Rohwerttyp angegeben ist.

debugPrint(_:)& String(reflecting:)kann für einen vollqualifizierten Namen verwendet werden:

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

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

Beachten Sie, dass Sie anpassen können, was in jedem dieser Szenarien gedruckt wird:

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

(Ich habe keine Möglichkeit gefunden, diesen "Standard" -Wert aufzurufen, um beispielsweise "Die Stadt ist Melbourne" zu drucken, ohne auf eine switch-Anweisung zurückzugreifen. Die Verwendung \(self)bei der Implementierung von description/ debugDescriptionverursacht eine unendliche Rekursion.)


Die Kommentare über String‚s init(_:)& init(reflecting:)initializers beschreiben genau das, was gedruckt wird, je nachdem , was die reflektierten Typ richtet sich:

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


Siehe die Release Notes für Informationen zu dieser Änderung.

Stuart
quelle
8
Auch wenn Sie den Zeichenfolgenwert ohne Verwendung möchten print(enum), können SieString(enum)
Kametrixom
44
Wichtiger Haken, dies funktioniert nur für Swift-Enums. Wenn Sie es mit @objc markieren, um die Bindungsunterstützung unter OS X zu ermöglichen, funktioniert dies nicht.
Claus Jørgensen
11
Große Swift-spezifische Antwort; Wenn Sie dies jedoch für eine nicht schnelle Aufzählung tun müssen, z. B. um CLAuthorizationStatusden Wert der Aufzählung (Ziel C) in Ihrem locationManager didChangeAuthorizationStatusDelegatenrückruf zu drucken , müssen Sie eine Protokollerweiterung definieren. Zum Beispiel: extension CLAuthorizationStatus: CustomStringConvertable { public var description: String { switch self { case .AuthorizedAlways: return "AuthorizedAlways" <etc> } } }- Sobald Sie dies getan haben, sollte es wie erwartet funktionieren: print ("Auth status: (\ status))".
Jeffro
3
"Ab Xcode 7 Beta 5" ist bedeutungslos. Es ist nicht Xcode, der dies definiert, sondern der Swift-Compiler und die Swift Runtime Libaries. Ich kann Xcode 9.3 verwenden, aber mein Code kann immer noch Swift 3 sein, und dann kann ich Swift 4-Funktionen nicht verwenden. Mit Xcode 9.3 funktioniert dieser Code nicht, obwohl Xcode 9.3 viel neuer als Xcode 7 ist.
Mecki
8
Ich habe den Initialisierer 'init (_ :)' erhalten, der erfordert, dass City mit 'LosslessStringConvertible' auf xcode 10.2, Swift 5 übereinstimmt. Wie machen wir das jetzt?
Rockgecko
73

Derzeit gibt es keine Selbstbeobachtung von Enum-Fällen. Sie müssen sie jeweils manuell deklarieren:

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

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

Wenn der Rohtyp ein Int sein soll, müssen Sie selbst einen Wechsel vornehmen:

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"
      }
    }
  }
}
Drawag
quelle
2
Noob Frage, aber warum setzen Sie get {return self.rawValue} anstatt nur self.value zurückzugeben? Ich habe letzteres ausprobiert und es funktioniert einwandfrei.
Chuck Krutsinger
Sie können das get { ... }Teil der Kürze halber auch weglassen, wenn Sie keinen Setter definieren.
Iosdude
1
Danke für die tolle Antwort. In Xcode 7.3 wird Folgendes angezeigt: "Printable wurde in CustomStringConvertible umbenannt". Die Lösung ist einfach: Ändern Sie im ersten Codebeispiel oben die erste Zeile in enum City : String, CustomStringConvertible {. Als Teil des CSC-Protokolls müssen Sie dann die Eigenschaft ändern, um öffentlich zu sein , zum Beispiel:public var description : String {
Jeffro
44

In Swift-3 (getestet mit Xcode 8.1) können Sie die folgenden Methoden in Ihre Aufzählung aufnehmen:

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

Sie können es dann als normalen Methodenaufruf für Ihre Enum-Instanz verwenden. Es könnte auch in früheren Swift-Versionen funktionieren, aber ich habe es nicht getestet.

In Ihrem Beispiel:

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"

Wenn Sie diese Funktionalität für alle Ihre Aufzählungen bereitstellen möchten, können Sie sie zu einer Erweiterung machen:

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

Dies funktioniert nur für Swift-Enums.

Matthias Voss
quelle
18

Für Objective- enumCs scheint der einzige Weg derzeit beispielsweise darin zu bestehen, die Aufzählung so zu erweitern, dass am Ende Folgendes CustomStringConvertibleendet:

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

Und dann das enumals String:

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

Der String(describing:)Initialisierer kann verwendet werden, um den Namen der Fallbezeichnung auch für Aufzählungen mit Nicht-String-Rohwerten zurückzugeben:

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

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

Beachten Sie, dass dies nicht funktioniert, wenn die Aufzählung den @objcModifikator verwendet:

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

Generierte Swift-Schnittstellen für Objective-C-Typen enthalten manchmal nicht den @objcModifikator. Diese Aufzählungen sind dennoch in Ziel-C definiert und funktionieren daher nicht wie oben.

pkamb
quelle
7

Zusätzlich zur Unterstützung von String (…) (CustomStringConvertible) für Enums in Swift 2.2 gibt es auch eine etwas defekte Reflection-Unterstützung für diese. Für Enum-Fälle mit zugehörigen Werten ist es möglich, die Bezeichnung des Enum-Falls mithilfe von Reflection zu erhalten:

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"

Mit "gebrochen" meinte ich jedoch, dass für "einfache" Aufzählungen die obige reflexionsbasierte labelberechnete Eigenschaft nur zurückkehrt nil(boo-hoo).

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

Die Situation mit Reflexion sollte sich anscheinend nach Swift 3 verbessern. Die Lösung für den Moment ist jedoch String(…), wie in einer der anderen Antworten vorgeschlagen:

print(String(City.Chelyabinsk)) // prints out Cheylabinsk
mz2
quelle
2
Dies scheint auf Swift 3.1 zu funktionieren, ohne dass es optional sein muss: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

Das ist so enttäuschend.

Für den Fall, dass Sie diese Namen benötigen (dass der Compiler die genaue Schreibweise genau kennt, aber den Zugriff verweigert - danke Swift-Team !! -), aber String nicht zur Basis Ihrer Aufzählung machen möchten oder können, a Die ausführliche, umständliche Alternative lautet wie folgt:

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

Sie können das Obige wie folgt verwenden:

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

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

Und Sie erhalten das erwartete Ergebnis (Code für die Spalte ähnlich, aber nicht angezeigt)

fetching element Title, column: Collections, row: 0

Oben habe ich die descriptionEigenschaft auf die stringMethode zurückführen lassen , aber dies ist Geschmackssache. Beachten Sie auch, dass sogenannte staticVariablen durch den Namen ihres umschließenden Typs bereichsqualifiziert werden müssen, da der Compiler zu amnesisch ist und den Kontext nicht alleine aufrufen kann ...

Das Swift-Team muss wirklich kommandiert werden. Sie haben eine Aufzählung erstellt, die Sie nicht können enumerateund für die Sie enumerate"Sequenzen" verwenden können, aber nicht enum!

verec
quelle
Das scheint ziemlich langwierig zu sein, als nur den String (reflektierend: self) in der Beschreibung zurückzugeben.
Boon
4

Ich bin auf diese Frage gestoßen und wollte einen einfachen Weg teilen, um die erwähnte magicFunction zu erstellen

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

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

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

Swift hat jetzt den sogenannten implizit zugewiesenen Rohwert . Wenn Sie nicht jedem Fall Rohwerte zuweisen und die Aufzählung vom Typ String ist, wird grundsätzlich abgeleitet, dass der Rohwert des Falls selbst im Zeichenfolgenformat vorliegt. Probieren Sie es weiter aus.

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

let city = City.Melbourne.rawValue

// city is "Melbourne"
NSCoder
quelle
3

Für 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"
        }
    }

}

Wenn Ihre Variable "Batteriezustand" lautet, rufen Sie auf:

self.batteryState.description
xevser
quelle
1

Einfach aber funktioniert ...

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
quelle