91

I have this enum with String values, which will be used to tell an API method that logs to a server what kind of serverity a message has. I'm using Swift 1.2, so enums can be mapped to Objective-C

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

I get the error

@objc enum raw type String is not an integer type

I haven't managed to find anywhere which says that only integers can be translated to Objective-C from Swift. Is this the case? If so, does anyone have any best-practice suggestion on how to make something like this available in Objective-C?

nhgrif
  • 61,578
  • 25
  • 134
  • 173
bogen
  • 9,954
  • 9
  • 50
  • 89

10 Answers10

101

One of the solutions is to use the RawRepresentable protocol.

It's not ideal to have to write the init and rawValue methods but that allows you to use this enum as usual in both Swift and 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:
                return nil
        }
    }
}
Remy Cilia
  • 2,573
  • 1
  • 20
  • 31
  • 10
    I really like this approach. To make it perfect, one may avoid some code duplication by defining a dictionary of type `[LogSeverity: String]` and then the `rawValue` and `init?` methods can be defined by a single line. – Gobe Nov 28 '16 at 16:43
  • property define in swift as > public var webViewType : WebViewType? is not found when i try to set it in OBJC class > webViewController.webViewType = WebViewType.TermsOfUse – Zeeshan Nov 01 '17 at 07:34
  • @Remy Cilia does it work in latest swift 4.1? I don't see any related symbols except typealias Swift_Enum in ProductModule-Swift.h generated header. – gaussblurinc May 18 '18 at 11:56
  • 2
    @Gobe can you share the example of how to write the rawValue and init? methods in a single line, please? – Daniel Sanchez Oct 31 '18 at 22:14
  • 2
    @DanielSanchez if you have an enum of type `LogSeverity` which is raw representable by `String`s, and you define once a dictionary of type `[LogSeverity: String]`, then the rawValue is simply `myDictionary[self]` and the init is `self = myDictionary.first(where: { $0.value == rawValue })` – Gobe Nov 01 '18 at 20:16
  • What if we have associated values in enum? What is the best approach to make it available at Objective C? – Dragisa Dragisic Aug 07 '19 at 11:52
  • I am getting error `Type 'LogSeverity.Constants' has no member 'routes'` – beowulf Oct 14 '19 at 10:58
  • 1
    It seems a nice answer, but I'm getting strange bad_access crashes after trying this out. – Jafar Khoshtabiat Feb 08 '20 at 03:23
  • @JafarKhoshtabiat `switch` is the keyword, not `swift` – Vladimirs Matusevics Feb 17 '20 at 14:07
  • 1
    @VladimirsMatusevics I fixed it and requested for edit, tnx for correction – Jafar Khoshtabiat Feb 18 '20 at 06:54
  • 1
    I'm confused a little: after generating `LogSeverity` enum into swift.h header file, how to access the `rawValue` by enum case in objc code? There is not enum method concept in objc side, right? – Itachi Mar 24 '21 at 11:16
  • @Itachi, any solution found? – Aaban Tariq Murtaza Oct 08 '21 at 07:49
  • 2
    rawValue isn't accessible in objc. – Aaban Tariq Murtaza Oct 08 '21 at 08:03
63

From the Xcode 6.3 release notes (emphasis added):

Swift Language Enhancements

...
Swift enums can now be exported to Objective-C using the @objc attribute. @objc enums must declare an integer raw type, and cannot be generic or use associated values. Because Objective-C enums are not namespaced, enum cases are imported into Objective-C as the concatenation of the enum name and case name.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
20

Here's a solution that works.

@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"
        }
    }
}
David
  • 14,205
  • 20
  • 97
  • 144
  • 14
    And how would the `name()` function be called in Objective-C? – Stephen Paul Nov 02 '15 at 20:05
  • @StephenPaul Something like ConnectivityStatusWifi. If you type that in you should see Xcode try to autocomplete. – David Nov 03 '15 at 22:34
  • 11
    @David but you cannot call `name()` in objc – Cameron Askew Nov 11 '15 at 02:29
  • I think you should make declare the function as public – i89 May 02 '16 at 08:15
  • 1
    @Chuck even public function will not expose the method. – Priyal Jan 29 '18 at 08:03
  • @David I got enum `public enum TrackingValue { case constant(String) case customVariable(name: String) case defaultVariable(DefaultVariable) public enum DefaultVariable { case advertisingId case advertisingTrackingEnabled case appVersion case connectionType case interfaceOrientation case isFirstEventAfterAppUpdate case requestQueueSize case adClearId } }` what will be best way to make it to be seen as ObjC enum? Thanks in advance! – Dragisa Dragisic Aug 06 '19 at 08:48
  • 4
    Don't know why this answer got up voted, this is not accessible from Obj-C – strangetimes Mar 03 '20 at 11:08
  • 1
    Yes i also can also not call from objc – guru May 27 '20 at 16:45
14

Here is work around if you really want to achieve the goal. However, you can access the enum values in objects that Objective C accepts, not as actual enum values.

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

To call :

NSString *debugRawValue = [LogSeverityBridge Debug]
BLC
  • 2,240
  • 25
  • 27
  • 1
    The problem I see with this is that you can't have `variables` of type LogSeverity, but otherwise OK. – Chris Prince May 06 '16 at 20:53
  • 3
    This worked for me with some minor alterations. `@objcMembers public class LogSeverityBridge: NSObject { static func debug() -> String { return TravelerProtectionLevel.premium.rawValue }` – Hendrix Dec 29 '17 at 22:51
9

If you don't mind to define the values in (Objective) C, you can use the NS_TYPED_ENUM macro to import constants in Swift.

For example:

.h file

typedef NSString *const ProgrammingLanguage NS_TYPED_ENUM;

FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageSwift;
FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageObjectiveC;

.m file

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

In Swift, this is imported as a struct as such:

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

Although the type is not bridged as an enum, it feels very similar to one when using it in Swift code.

You can read more about this technique in Grouping Related Objective-C Constants

David H
  • 40,852
  • 12
  • 92
  • 138
RvdB
  • 868
  • 7
  • 7
  • 1
    This is exactly the approach I was looking for! – Tom Kraina May 09 '19 at 10:55
  • Of all the solutions I like this one the best, using it in a big mixed Objective-C and Swift project. – David H Jul 29 '22 at 20:37
  • I use NS_TYPED_EXTENSIBLE_ENUM to be able to add more cases over time. Is there a way to make extensions done in Swift available to Objective-C? Marking them with @objc does not work since the extended type is the bridged struct and not an Objective-C type like NSString. – DEAD10CC Mar 21 '23 at 12:31
5

Code for Xcode 8, using the fact that Int works but other methods aren't exposed to Objective-C. This is pretty horrible as it stands...

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"
        }
    }
}
shim
  • 9,289
  • 12
  • 69
  • 108
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
4

This is my use case:

  • I avoid hard-coded Strings whenever I can, so that I get compile warnings when I change something
  • I have a fixed list of String values coming from a back end, which can also be nil

Here's my solution that involves no hard-coded Strings at all, supports missing values, and can be used elegantly in both Swift and 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
        }
    }
}
Chris Garrett
  • 4,824
  • 1
  • 34
  • 49
  • 1
    I cannot use `[InventoryItemType fromString:]` from objective-c: it gives the error "Receiver type 'InventoryItemType' is not an Objective-C class" – agirault Oct 11 '18 at 14:55
  • 1
    That's because in Objective-C InventoryItemType will be of type Int or NSInteger, it's not a class. – Chris Garrett Dec 11 '18 at 14:07
  • 1
    Hi @ChrisGarrett I got enum like: `public enum TrackingValue { case constant(String) case customVariable(name: String) case defaultVariable(DefaultVariable) public enum DefaultVariable { case advertisingId case advertisingTrackingEnabled case appVersion case connectionType case interfaceOrientation case isFirstEventAfterAppUpdate case requestQueueSize case adClearId } }` what will be best way to make it to be seen as ObjC enum? Thanks in advance! – Dragisa Dragisic Aug 06 '19 at 09:02
  • 1
    @agirault you can wrap it in another class, e.g. ```class InventoryItemTypeParser: NSObject { @objc static func fromString(_ string: String?) -> InventoryItemType { return InventoryItemType.fromString(string) } }``` – Krzysztof Skrzynecki Feb 13 '20 at 12:45
2

Here's what I came up with. In my case, this enum was in the context providing info for a specific class, 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"
        }
    }
}

From Swift, you can use .string on an enum (similar to .rawValue). From Objective-C, you can use [ServiceProvider FieldNameToString:enumValue];

Chris Prince
  • 7,288
  • 2
  • 48
  • 66
1

You can create an private Inner enum. The implementation is a bit repeatable, but clear and easy. 1 line rawValue, 2 lines init, which always look the same. The Inner has a method returning the "outer" equivalent, and vice-versa.

Has the added benefit that you can directly map the enum case to a String, unlike other answers here.

Please feel welcome to build on this answer if you know how to solve the repeatability problem with templates, I don't have time to mingle with it right now.

@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
            }
        }
    }
}
Pawel Jurczyk
  • 165
  • 2
  • 10
1

I think @Remi 's answer crashes in some situations as I had this:

My error's screesshot. so I post my edition for @Remi 's answer:

@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:
                return nil
        }
    }
}