Comment obtenir le nom de la valeur d'énumération dans Swift?

167

Si j'ai une énumération avec des Integervaleurs brutes :

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

let city = City.Melbourne

Comment puis-je convertir une cityvaleur en chaîne Melbourne? Ce type d'introspection de nom de type est-il disponible dans la langue?

Quelque chose comme (ce code ne fonctionnera pas):

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

Réponses:

139

À partir de Xcode 7 beta 5 (Swift version 2), vous pouvez désormais imprimer les noms de type et les cas d'énumération par défaut en utilisant print(_:), ou convertir en Stringutilisant Stringl' init(_:)initialiseur ou la syntaxe d'interpolation de chaîne. Donc pour votre exemple:

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"

Il n'est donc plus nécessaire de définir et de maintenir une fonction pratique qui active chaque cas pour renvoyer une chaîne littérale. De plus, cela fonctionne automatiquement pour toute énumération, même si aucun type de valeur brute n'est spécifié.

debugPrint(_:)& String(reflecting:)peut être utilisé pour un nom complet:

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

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

Notez que vous pouvez personnaliser ce qui est imprimé dans chacun de ces scénarios:

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

(Je n'ai pas trouvé de moyen d'appeler cette valeur "par défaut", par exemple, pour afficher "La ville est Melbourne" sans recourir à une instruction switch. L'utilisation \(self)dans l'implémentation de description/ debugDescriptionprovoque une récursivité infinie.)


Les commentaires ci - dessus Stringde » init(_:)& init(reflecting:)initializers décrivent exactement ce qui est imprimé, en fonction de ce que répond le Type réfléchi à:

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


Consultez les notes de version pour plus d'informations sur ce changement.

Stuart
la source
8
Aussi si vous voulez la valeur de chaîne sans utiliser, print(enum)vous pouvez utiliserString(enum)
Kametrixom
44
Prise importante, cela ne fonctionne que pour les énumérations Swift. Si vous le marquez @objc pour autoriser la prise en charge de la liaison sur OS X, cela ne fonctionnera pas.
Claus Jørgensen le
11
Grande réponse spécifique à Swift; cependant, si vous avez besoin de le faire sur une énumération non rapide, comme pour CLAuthorizationStatusafficher la valeur de l'énumération (Objective C) dans votre locationManager didChangeAuthorizationStatusrappel de délégué, vous devez définir une extension de protocole. Par exemple: extension CLAuthorizationStatus: CustomStringConvertable { public var description: String { switch self { case .AuthorizedAlways: return "AuthorizedAlways" <etc> } } }- une fois que vous avez fait cela, cela devrait fonctionner comme prévu: print ("Auth status: (\ status))".
Jeffro
3
«À partir de Xcode 7 beta 5» n'a pas de sens. Ce n'est pas Xcode qui définit quoi que ce soit, c'est le compilateur Swift et les bibliothèques Swift Runtime. Je peux utiliser Xcode 9.3 mais mon code peut toujours être Swift 3 et je ne pourrai pas utiliser les fonctionnalités de Swift 4. En utilisant Xcode 9.3, ce code ne fonctionne pas bien que Xcode 9.3 soit beaucoup plus récent que Xcode 7.
Mecki
8
J'ai l'initialiseur «init (_ :)» exige que City se conforme à «LosslessStringConvertible» sur xcode 10.2, Swift 5. Comment le faisons-nous maintenant?
rockgecko
73

Il n'y a pas d'introspection sur les cas d'énumération pour le moment. Vous devrez les déclarer chacun manuellement:

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

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

Si vous avez besoin que le type brut soit un Int, vous devrez faire un changement vous-même:

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"
      }
    }
  }
}
dessiner
la source
2
Noob question, mais pourquoi mettre get {return self.rawValue} au lieu de simplement renvoyer self.value? J'ai essayé ce dernier et cela fonctionne très bien.
Chuck Krutsinger
Vous pouvez également omettre la get { ... }partie par souci de concision si vous ne définissez pas de setter.
iosdude
1
Merci pour la bonne réponse. Dans Xcode 7.3, j'obtiens: "Printable a été renommé CustomStringConvertible". La solution est simple: dans le premier exemple de code ci-dessus, remplacez la première ligne par enum City : String, CustomStringConvertible {. Dans le cadre du protocole CSC, vous devrez ensuite changer la propriété pour qu'elle soit publique , par exemple:public var description : String {
Jeffro
44

Dans Swift-3 (testé avec Xcode 8.1), vous pouvez ajouter les méthodes suivantes dans votre énumération:

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

Vous pouvez ensuite l'utiliser comme un appel de méthode normal sur votre instance enum. Cela pourrait également fonctionner dans les versions précédentes de Swift, mais je ne l'ai pas testé.

Dans votre exemple:

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 vous souhaitez fournir cette fonctionnalité à toutes vos énumérations, vous pouvez en faire une extension:

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

Cela ne fonctionne que pour les énumérations Swift.

Matthias Voss
la source
18

Pour les Objective-C, enumle seul moyen semble actuellement être, par exemple, d'étendre l'énumération en CustomStringConvertibleaboutissant à quelque chose comme:

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

Et puis lancer le enumcomme String:

String(UIDevice.currentDevice().batteryState)
Markus Rautopuro
la source
12

L' String(describing:)initialiseur peut être utilisé pour renvoyer le nom de l'étiquette de cas, même pour les énumérations avec des valeurs 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"

Notez que cela ne fonctionne pas si l'énumération utilise le @objcmodificateur:

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

Les interfaces Swift générées pour les types Objective-C n'incluent parfois pas le @objcmodificateur. Ces Enums sont néanmoins définis en Objective-C, et ne fonctionnent donc pas comme ci-dessus.

pkamb
la source
7

En plus du support String (…) (CustomStringConvertible) pour les énumérations dans Swift 2.2, il y a aussi un support de réflexion quelque peu cassé. Pour les cas d'énumération avec des valeurs associées, il est possible d'obtenir l'étiquette de cas d'énumération en utilisant la réflexion:

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"

En étant cassé, je voulais dire que pour les énumérations "simples", la labelpropriété calculée basée sur la réflexion ci-dessus renvoie juste nil(boo-hoo).

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

La situation avec réflexion devrait s'améliorer après Swift 3, apparemment. La solution pour l'instant est cependant String(…), comme suggéré dans l'une des autres réponses:

print(String(City.Chelyabinsk)) // prints out Cheylabinsk
mz2
la source
2
Cela semble fonctionner sur Swift 3.1 sans avoir besoin de le rendre facultatif: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

C'est tellement décevant.

Pour le cas où vous avez besoin de ces noms (dont le compilateur connaît parfaitement l'orthographe exacte, mais refuse de laisser l'accès - merci l'équipe Swift !! -) mais que vous ne voulez pas ou ne pouvez pas faire de String la base de votre énumération, un L'alternative verbeuse et lourde est la suivante:

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

Vous pouvez utiliser ce qui précède comme suit:

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

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

Et vous obtiendrez le résultat attendu (code pour la colonne similaire, mais non affiché)

fetching element Title, column: Collections, row: 0

Dans ce qui précède, j'ai fait descriptionrenvoyer la propriété à la stringméthode, mais c'est une question de goût. Notez également que les soi-disant staticvariables doivent être qualifiées de portée par le nom de leur type englobant, car le compilateur est trop amnésique et ne peut pas rappeler le contexte tout seul ...

L'équipe Swift doit vraiment être commandée. Ils ont créé des énumérations que vous ne pouvez pas enumerateet que vous pouvez utiliser enumeratesont des "séquences" mais pas enum!

verec
la source
Cela semble plutôt long que de simplement retourner String (reflétant: self) dans la description.
Boon le
4

Je suis tombé sur cette question et je voulais partager un moyen simple de créer la magicFunction mentionnée

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

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

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

Swift a maintenant ce que l'on appelle une valeur brute implicitement attribuée . Fondamentalement, si vous ne donnez pas de valeurs brutes à chaque cas et que l'énumération est de type String, cela en déduit que la valeur brute du cas est elle-même au format chaîne. Continuez à essayer.

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

let city = City.Melbourne.rawValue

// city is "Melbourne"
NSCoder
la source
3

Pour 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 votre variable "batteryState" alors appelez:

self.batteryState.description
xevser
la source
1

Simple mais fonctionne ...

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
la source
0

L'introspection dans Swift Enums semble fonctionner partiellement.

J'ai vu la réponse de @ drewag et j'ai découvert qu'un Enum sans rawValues ​​peut en effet avoir une introspection dans Swift 5.X avec Xcode 11.5. Ce code fonctionne.

public enum Domain: String {
    case network
    case data
    case service
    case sync
    var description: String {
        return "\(self)"     // THIS INTROSPECTION WORKS
    }
}
enum ErrorCode: Int, CustomStringConvertible {
    case success = 200
    case created = 201
    case accepted = 202
    case badRequest = 400
    case unauthorized = 401
    case forbidden = 403
    case notFound = 404
    var code: Int {
        return self.rawValue
    }
    var description: String {
        return "\(self)"      //THIS DOES NOT WORK - EXEC_BAD_ACCESS
    }
}
let errorCode = ErrorCode.notFound
let domain = Domain.network
print(domain.description, errorCode.code, errorCode.description)

Remplacez le "\(self)"for "string"dans la seconde Enumet vous obtiendrez cette impression: chaîne réseau 404

REMARQUE: utiliser String(self)au lieu du protocole "\(self)" in the first Enum will require the Enum to conform to theLosslessStringConvertible` et ajouter également d'autres initialiseurs, de sorte qu'une interpolation de chaîne semble être une bonne solution de contournement.

Pour ajouter un var description: Stringà l'énumération, vous devrez utiliser une instruction Switch pour tous les cas d'énumération comme indiqué précédemment

var description: String {
    switch self {
    case .success: return "Success"
    case .created: return "Created"
    case .accepted: return "Accepted"
    }
}
eharo2
la source