web-dev-qa-db-fra.com

Comment rendre un enum String Swift disponible dans Objective-C?

J'ai cette énumération avec les valeurs String qui seront utilisées pour indiquer à une méthode d'API qui enregistre sur un serveur le type de serveur d'un message. J'utilise Swift 1.2, donc les énumérations peuvent être mappées à Objective-C

@objc enum LogSeverity : String {
    case Debug = "DEBUG"
    case Info = "INFO"
    case Warn = "WARN"
    case Error = "ERROR"
}

Je reçois l'erreur 

@objc enum raw type La chaîne n'est pas un type entier

Je n'ai réussi à trouver nulle part où il est indiqué que seuls les entiers peuvent être traduits en Objective-C à partir de Swift. Est-ce le cas? Si tel est le cas, quelqu'un a-t-il une suggestion de meilleure pratique sur la manière de rendre disponible un tel résultat dans Objective-C?

46
bogen

À partir des notes de version de Xcode 6.3 } (soulignement ajouté):

Améliorations du langage rapide 

...
Les énumérations rapides peuvent maintenant être exportées vers Objective-C à l’aide de @objc attribut. @objc enums doit déclarer un type brut entier, et ne peut pas être générique ou utiliser des valeurs associées. Parce que les enums Objective-C ne sont pas namespaced, les requêtes enum sont importées dans Objective-C en tant que concaténation du nom enum et du nom de l’affaire.

45
Martin R

L'une des solutions consiste à utiliser le protocole RawRepresentable.

Ce n'est pas idéal d'avoir à écrire les méthodes init et rawValue mais cela vous permet d'utiliser cette énumération comme d'habitude dans Swift et Objective-C.

@objc public enum LogSeverity: Int, RawRepresentable {
    case Debug
    case Info
    case Warn
    case Error

    public typealias RawValue = String

    public var rawValue: RawValue {
        switch self {
            case .Debug:
                return "DEBUG"
            case .Info:
                return "INFO"
            case .Warn:
                return "WARN"
            case .Error:
                return "ERROR"
        }
    }

    public init?(rawValue: RawValue) {
        switch rawValue {
            case "DEBUG":
                self = .Debug
            case "INFO":
                self = .Info
            case "WARN":
                self = .Warn
            case "ERROR":
                self = .Error
            default:
                self = .Debug
        }
    }
}
44
Remy Cilia

Voici une solution qui fonctionne.

@objc public enum ConnectivityStatus: Int {
    case Wifi
    case Mobile
    case Ethernet
    case Off

    func name() -> String {
        switch self {
        case .Wifi: return "wifi"
        case .Mobile: return "mobile"
        case .Ethernet: return "ethernet"
        case .Off: return "off"
        }
    }
}
20
David

Voici un travail autour si vous voulez vraiment atteindre l'objectif. Cependant, vous pouvez accéder aux valeurs enum dans les objets acceptés par Objective C, et non en tant que valeurs enum réelles.

enum LogSeverity : String {

    case Debug = "DEBUG"
    case Info = "INFO"
    case Warn = "WARN"
    case Error = "ERROR"

    private func string() -> String {
        return self.rawValue
    }
}

@objc
class LogSeverityBridge: NSObject {

    class func Debug() -> NSString {
        return LogSeverity.Debug.string()
    }

    class func Info() -> NSString {
        return LogSeverity.Info.string()
    }

    class func Warn() -> NSString {
        return LogSeverity.Warn.string()
    }

    class func Error() -> NSString {
        return LogSeverity.Error.string()
    }
}

Appeler :

NSString *debugRawValue = [LogSeverityBridge Debug]
5
BLC

Code pour Xcode 8, en utilisant le fait que Int fonctionne mais que les autres méthodes ne sont pas exposées à Objective-C. C'est assez horrible tel qu'il se présente ... 

class EnumSupport : NSObject {
    class func textFor(logSeverity severity: LogSeverity) -> String {
        return severity.text()
    }
}

@objc public enum LogSeverity: Int {
    case Debug
    case Info
    case Warn
    case Error

    func text() -> String {
        switch self {
            case .Debug: return "debug"
            case .Info: return "info"
            case .Warn: return "warn"
            case .Error: return "error"
        }
    }
}
3
Dan Rosenstark

Si cela ne vous dérange pas de définir les valeurs dans (Objective) C, vous pouvez utiliser la macro NS_TYPED_ENUM pour importer des constantes dans Swift.

Par exemple:

fichier .h

typedef NSString *const ProgrammingLanguage NS_TYPED_ENUM;

FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageSwift;
FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageObjectiveC;

fichier .m

ProgrammingLanguage ProgrammingLanguageSwift = "Swift";
ProgrammingLanguage ProgrammingLanguageObjectiveC = "ObjectiveC";

Dans Swift, ceci est importé en tant que struct en tant que tel:

struct ProgrammingLanguage: RawRepresentable, Equatable, Hashable {
    typealias RawValue = String

    init(rawValue: RawValue)
    var rawValue: RawValue { get }

    static var Swift: ProgrammingLanguage { get }
    static var objectiveC: ProgrammingLanguage { get }
}

Bien que le type ne soit pas ponté en tant que enum, il est très similaire à celui-ci lorsqu’il est utilisé dans le code Swift.

Pour en savoir plus sur cette technique, consultez la section "Interaction avec les API C" de Utilisation de Swift avec Cocoa et de la documentation Objective-C

2
RvdB

Vous pouvez créer une énumération privée Inner. L'implémentation est un peu répétable, mais claire et facile. 1 ligne rawValue, 2 lignes init, qui se ressemblent toujours. La variable Inner a une méthode renvoyant l'équivalent "externe", et inversement. 

L’avantage supplémentaire que vous pouvez mapper directement le cas d’énumération à un String, contrairement à d’autres réponses ici.

N'hésitez pas à nous appuyer sur cette réponse si vous savez comment résoudre le problème de répétabilité avec des modèles, je n'ai pas le temps de le mélanger pour le moment.

@objc enum MyEnum: NSInteger, RawRepresentable, Equatable {
    case
    option1,
    option2,
    option3

    // MARK: RawRepresentable

    var rawValue: String {
        return toInner().rawValue
    }

    init?(rawValue: String) {
        guard let value = Inner(rawValue: rawValue)?.toOuter() else { return nil }
        self = value
    }

    // MARK: Obj-C support

    private func toInner() -> Inner {
        switch self {
        case .option1: return .option1
        case .option3: return .option3
        case .option2: return .option2
        }
    }

    private enum Inner: String {
        case
        option1 = "option_1",
        option2 = "option_2",
        option3 = "option_3"

        func toOuter() -> MyEnum {
            switch self {
            case .option1: return .option1
            case .option3: return .option3
            case .option2: return .option2
            }
        }
    }
}
1
Pawel Jurczyk

Voici ce que je suis venu avec. Dans mon cas, cette énumération était dans le contexte fournissant des informations pour une classe spécifique, ServiceProvider.

class ServiceProvider {
    @objc enum FieldName : Int {
        case CITY
        case LATITUDE
        case LONGITUDE
        case NAME
        case GRADE
        case POSTAL_CODE
        case STATE
        case REVIEW_COUNT
        case COORDINATES

        var string: String {
            return ServiceProvider.FieldNameToString(self)
        }
    }

    class func FieldNameToString(fieldName:FieldName) -> String {
        switch fieldName {
        case .CITY:         return "city"
        case .LATITUDE:     return "latitude"
        case .LONGITUDE:    return "longitude"
        case .NAME:         return "name"
        case .GRADE:        return "overallGrade"
        case .POSTAL_CODE:  return "postalCode"
        case .STATE:        return "state"
        case .REVIEW_COUNT: return "reviewCount"
        case .COORDINATES:  return "coordinates"
        }
    }
}

À partir de Swift, vous pouvez utiliser .string sur un enum (similaire à .rawValue) . À partir de Objective-C, vous pouvez utiliser [ServiceProvider FieldNameToString:enumValue];

1
Chris Prince

Ceci est mon cas d'utilisation:

  • J'évite les chaînes codées en dur chaque fois que je le peux, afin d'obtenir des avertissements de compilation lorsque je change quelque chose
  • J'ai une liste fixe de valeurs de chaîne venant d'un back-end, qui peut aussi être nul

Voici ma solution qui n'implique aucune chaîne codée en dur, qui prend en charge les valeurs manquantes et peut être utilisée avec élégance dans Swift et Obj-C:

@objc enum InventoryItemType: Int {
    private enum StringInventoryItemType: String {
        case vial
        case syringe
        case crystalloid
        case bloodProduct
        case supplies
    }

    case vial
    case syringe
    case crystalloid
    case bloodProduct
    case supplies
    case unknown

    static func fromString(_ string: String?) -> InventoryItemType {
        guard let string = string else {
            return .unknown
        }
        guard let stringType = StringInventoryItemType(rawValue: string) else {
            return .unknown
        }
        switch stringType {
        case .vial:
            return .vial
        case .syringe:
            return .syringe
        case .crystalloid:
            return .crystalloid
        case .bloodProduct:
            return .bloodProduct
        case .supplies:
            return .supplies
        }
    }

    var stringValue: String? {
        switch self {
        case .vial:
            return StringInventoryItemType.vial.rawValue
        case .syringe:
            return StringInventoryItemType.syringe.rawValue
        case .crystalloid:
            return StringInventoryItemType.crystalloid.rawValue
        case .bloodProduct:
            return StringInventoryItemType.bloodProduct.rawValue
        case .supplies:
            return StringInventoryItemType.supplies.rawValue
        case .unknown:
            return nil
        }
    }
}
0
Chris Garrett