Jak zdobyć nazwę wartości wyliczenia w Swift?

167

Jeśli mam wyliczenie z surowymi Integerwartościami:

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

let city = City.Melbourne

Jak przekonwertować citywartość na ciąg Melbourne? Czy tego rodzaju introspekcja nazw typów jest dostępna w danym języku?

Coś takiego (ten kod nie zadziała):

println("Your city is \(city.magicFunction)")
> Your city is Melbourne
Evgenii
źródło

Odpowiedzi:

139

Począwszy od Xcode 7 beta 5 (Swift Version 2) można teraz wydrukować nazwy typu i przypadki enum domyślnie przy użyciu print(_:), lub przekonwertować do Stringkorzystania String„s init(_:)składni inicjator lub ciąg interpolacji. Na przykład:

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"

Nie ma więc już potrzeby definiowania i utrzymywania wygodnej funkcji, która włącza każdy przypadek, aby zwrócić literał ciągu. Ponadto działa to automatycznie dla każdego wyliczenia, nawet jeśli nie określono typu wartości surowej.

debugPrint(_:)& String(reflecting:)może służyć do w pełni kwalifikowanej nazwy:

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

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

Pamiętaj, że możesz dostosować zawartość drukowaną w każdym z tych scenariuszy:

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

(Nie znalazłem sposobu, aby wywołać tę „domyślną” wartość, na przykład, aby wydrukować „Miasto to Melbourne” bez uciekania się do instrukcji przełącznika. Użycie \(self)w implementacji description/ debugDescriptionpowoduje nieskończoną rekurencję).


Powyższe komentarze String„s init(_:)& init(reflecting:)inicjalizatorów opisać dokładnie co jest drukowane, w zależności od typu odbitych Przylega do:

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


Zobacz informacje o wersji, aby uzyskać informacje o tej zmianie.

Stuart
źródło
8
Również jeśli chcesz uzyskać wartość ciągu bez użycia print(enum), możesz użyćString(enum)
Kametrixom
44
Ważny haczyk, działa to tylko w przypadku wyliczeń Swift. Jeśli oznaczysz go @objc, aby zezwolić na obsługę powiązań w systemie OS X, to nie zadziała.
Claus Jørgensen
11
Świetna odpowiedź specyficzna dla Swifta; jednak jeśli chcesz to zrobić na nie-swift wyliczeniu, na przykład wydrukować CLAuthorizationStatuswartość wyliczenia (celu C) wewnątrz locationManager didChangeAuthorizationStatuswywołania zwrotnego delegata, musisz zdefiniować rozszerzenie protokołu. Na przykład: extension CLAuthorizationStatus: CustomStringConvertable { public var description: String { switch self { case .AuthorizedAlways: return "AuthorizedAlways" <etc> } } }- po wykonaniu tej czynności powinno działać zgodnie z oczekiwaniami: print ("Auth status: (\ status))".
Jeffro
3
„Począwszy od Xcode 7 beta 5” nie ma znaczenia. To nie Xcode definiuje to wszystko, to kompilator Swift i biblioteki Swift Runtime Libaries. Mogę używać Xcode 9.3, ale mój kod nadal może być Swift 3 i wtedy nie będę mógł korzystać z funkcji Swift 4. Korzystając z Xcode 9.3, ten kod nie działa, mimo że Xcode 9.3 jest znacznie nowszy niż Xcode 7.
Mecki
8
Mam inicjator „init (_ :)” wymaga, aby City było zgodne z „LosslessStringConvertible” na xcode 10.2, Swift 5. Jak to zrobić teraz?
rockgecko
73

Obecnie nie ma introspekcji przypadków wyliczenia. Będziesz musiał zadeklarować je ręcznie:

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

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

Jeśli chcesz, aby typem surowym był Int, będziesz musiał samodzielnie dokonać zmiany:

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
źródło
2
Nie ma sprawy, ale po co wstawiać get {return self.rawValue} zamiast po prostu zwracać self.value? Wypróbowałem to drugie i działa dobrze.
Chuck Krutsinger
Możesz również pominąć get { ... }część dla zwięzłości, jeśli nie zdefiniujesz ustawiacza.
iosdude
1
Dzięki za świetną odpowiedź. W Xcode 7.3 otrzymuję: „Nazwa Printable została zmieniona na CustomStringConvertible”. Rozwiązanie jest proste - w pierwszym przykładzie kodu powyżej zmień pierwszą linię na enum City : String, CustomStringConvertible {. W ramach protokołu CSC będziesz musiał zmienić właściwość na publiczną , na przykład:public var description : String {
Jeffro
44

W Swift-3 (przetestowanym z Xcode 8.1) możesz dodać następujące metody w swoim wyliczeniu:

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

Następnie można go użyć jako normalnego wywołania metody w instancji wyliczenia. Może również działać w poprzednich wersjach Swift, ale nie testowałem tego.

W twoim przykładzie:

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"

Jeśli chcesz zapewnić tę funkcjonalność wszystkim swoim wyliczeniom, możesz uczynić z niej rozszerzenie:

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

Działa to tylko w przypadku wyliczeń Swift.

Matthias Voss
źródło
18

W przypadku Objective-C enumwydaje się obecnie, że jedynym sposobem jest, na przykład, rozszerzenie wyliczenia, CustomStringConvertiblekończąc na czymś takim:

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

A następnie rzucając enumjako String:

String(UIDevice.currentDevice().batteryState)
Markus Rautopuro
źródło
12

String(describing:)Inicjator może być używany do powrotu nazwę etykiety case nawet teksty stałe z rawValues 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"

Zauważ, że to nie zadziała, jeśli wyliczenie używa @objcmodyfikatora:

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

Generowane interfejsy Swift dla typów Objective-C czasami nie zawierają @objcmodyfikatora. Te wyliczenia są jednak zdefiniowane w Objective-C, a zatem nie działają jak powyżej.

pkamb
źródło
7

Oprócz obsługi typu String (…) (CustomStringConvertible) dla wyliczeń w Swift 2.2 istnieje również nieco zepsuta obsługa odbić. W przypadku przypadków wyliczeniowych ze skojarzonymi wartościami można uzyskać etykietę przypadku wyliczenia przy użyciu odbicia:

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"

Miałem jednak na myśli to, że dla „prostych” wyliczeń powyższa labelobliczona właściwość oparta na odbiciu po prostu zwraca nil(boo-hoo).

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

Najwyraźniej sytuacja z refleksją powinna się poprawić po Swift 3. Na razie rozwiązanie jest jednak String(…), jak sugerowano w jednej z pozostałych odpowiedzi:

print(String(City.Chelyabinsk)) // prints out Cheylabinsk
mz2
źródło
2
Wydaje się, że działa to w Swift 3.1 bez konieczności ustawiania go jako opcjonalnego: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

To jest takie rozczarowujące.

W przypadku, gdy potrzebujesz tych nazw (które kompilator doskonale zna dokładną pisownię, ale odmawia dostępu - dziękuję zespołowi Swift !! -), ale nie chcesz lub nie możesz uczynić z String bazy wyliczenia, a rozwlekła, uciążliwa alternatywa jest następująca:

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

Możesz użyć powyższego w następujący sposób:

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

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

Otrzymasz oczekiwany wynik (kod kolumny podobny, ale nie pokazany)

fetching element Title, column: Collections, row: 0

W powyższym odwołałem descriptionwłaściwość do stringmetody, ale to kwestia gustu. Zwróć również uwagę, że tak zwane staticzmienne muszą być określane jako zakres przez nazwę ich otaczającego typu, ponieważ kompilator jest zbyt amnestyczny i nie może samodzielnie przypomnieć sobie kontekstu ...

Zespół Swift naprawdę musi być dowodzony. Stworzyli enum, którego nie możesz, enumeratea na którym możesz użyć enumerate„Sekwencji”, ale nie enum!

verec
źródło
Wydaje się, że jest to raczej rozwlekłe niż zwykłe zwracanie String (odzwierciedlającego: siebie) w opisie.
Boon
4

Natknąłem się na to pytanie i chciałem podzielić się prostym sposobem stworzenia wspomnianej funkcji magicFunction

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

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

let city = City.Melbourne
city.magicFunction() //prints Melbourne
Sev
źródło
3

Swift ma teraz tak zwaną niejawnie przypisaną wartość surową . Zasadniczo, jeśli nie podasz surowych wartości dla każdego przypadku, a wyliczenie jest typu String, wywnioskuje, że nieprzetworzona wartość przypadku jest sama w formacie ciągu. Dalej, spróbuj.

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

let city = City.Melbourne.rawValue

// city is "Melbourne"
NSCoder
źródło
3

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

}

jeśli zmienna „batteryState”, wywołaj:

self.batteryState.description
xevser
źródło
1

Proste, ale działa ...

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
źródło