How to make a Swift String enum available in Objective-C?

60,892

Solution 1

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.

Solution 2

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

Solution 3

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

Solution 4

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]

Solution 5

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 the "Interacting with C APIs" of the Using Swift with Cocoa and Objective-C documentation

Share:
60,892

Related videos on Youtube

bogen
Author by

bogen

CTO @ Beining & Bogen

Updated on February 18, 2020

Comments

  • bogen
    bogen about 4 years

    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?

  • Stephen Paul
    Stephen Paul over 8 years
    And how would the name() function be called in Objective-C?
  • David
    David over 8 years
    @StephenPaul Something like ConnectivityStatusWifi. If you type that in you should see Xcode try to autocomplete.
  • Cameron Askew
    Cameron Askew over 8 years
    @David but you cannot call name() in objc
  • i89
    i89 about 8 years
    I think you should make declare the function as public
  • Chris Prince
    Chris Prince about 8 years
    The problem I see with this is that you can't have variables of type LogSeverity, but otherwise OK.
  • Gobe
    Gobe over 7 years
    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.
  • Zeeshan
    Zeeshan over 6 years
    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
  • Zack Shapiro
    Zack Shapiro over 6 years
    Page cannot be found on link
  • Hendrix
    Hendrix over 6 years
    This worked for me with some minor alterations. @objcMembers public class LogSeverityBridge: NSObject { static func debug() -> String { return TravelerProtectionLevel.premium.rawValue }
  • Priyal
    Priyal over 6 years
    @Chuck even public function will not expose the method.
  • gaussblurinc
    gaussblurinc about 6 years
    @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.
  • agirault
    agirault over 5 years
    I cannot use [InventoryItemType fromString:] from objective-c: it gives the error "Receiver type 'InventoryItemType' is not an Objective-C class"
  • Daniel Sanchez
    Daniel Sanchez over 5 years
    @Gobe can you share the example of how to write the rawValue and init? methods in a single line, please?
  • Gobe
    Gobe over 5 years
    @DanielSanchez if you have an enum of type LogSeverity which is raw representable by Strings, 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 })
  • Chris Garrett
    Chris Garrett over 5 years
    That's because in Objective-C InventoryItemType will be of type Int or NSInteger, it's not a class.
  • Tom Kraina
    Tom Kraina about 5 years
    This is exactly the approach I was looking for!
  • Dragisa Dragisic
    Dragisa Dragisic almost 5 years
    @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
    Dragisa Dragisic almost 5 years
    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
    Dragisa Dragisic almost 5 years
    What if we have associated values in enum? What is the best approach to make it available at Objective C?
  • beowulf
    beowulf over 4 years
    I am getting error Type 'LogSeverity.Constants' has no member 'routes'
  • Jafar Khoshtabiat
    Jafar Khoshtabiat over 4 years
    It seems a nice answer, but I'm getting strange bad_access crashes after trying this out.
  • Krzysztof Skrzynecki
    Krzysztof Skrzynecki over 4 years
    @agirault you can wrap it in another class, e.g. class InventoryItemTypeParser: NSObject { @objc static func fromString(_ string: String?) -> InventoryItemType { return InventoryItemType.fromString(string) } }
  • Vladimirs Matusevics
    Vladimirs Matusevics about 4 years
    @JafarKhoshtabiat switch is the keyword, not swift
  • Jafar Khoshtabiat
    Jafar Khoshtabiat about 4 years
    @VladimirsMatusevics I fixed it and requested for edit, tnx for correction
  • strangetimes
    strangetimes about 4 years
    Don't know why this answer got up voted, this is not accessible from Obj-C
  • Retro
    Retro about 4 years
    is there any way to create this enum from Objc, when I try [LogSeverity rawValue:] it doesn't find initializer.
  • OliverD
    OliverD almost 4 years
    How can I access the string value from objective c?
  • guru
    guru almost 4 years
    Yes i also can also not call from objc
  • Itachi
    Itachi about 3 years
    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?
  • Aaban Tariq Murtaza
    Aaban Tariq Murtaza over 2 years
    @Itachi, any solution found?
  • Aaban Tariq Murtaza
    Aaban Tariq Murtaza over 2 years
    rawValue isn't accessible in objc.