Reputation: 10432
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?
Upvotes: 96
Views: 72861
Reputation: 4919
A working solution based on the answer provided by @Remi-Cilia at https://stackoverflow.com/a/38490781/602249, for using string values in Objective-C:
@objcMembers public final class VideoTypeToString: NSObject {
public static func rawValue(of target: VideoType) -> String? {
target.rawValue
}
}
@objc public enum VideoType: Int, RawRepresentable {
case video
case clip
case video_message
case story
public typealias RawValue = String
public var rawValue: RawValue {
switch self {
case .video:
return "video"
case .clip:
return "clip"
case .video_message:
return "video_message"
case .story:
return "story"
}
}
public init?(rawValue: RawValue) {
switch rawValue {
case "video":
self = .video
case "clip":
self = .clip
case "video_message":
self = .video_message
case "story":
self = .story
default:
return nil
}
}
}
Usage in Objective-C code:
VideoType type = VideoTypeClip;
NSString *stringType = [VideoTypeToString rawValueOf:type];
Upvotes: 0
Reputation: 888
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
Upvotes: 9
Reputation: 2623
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
}
}
}
Upvotes: 103
Reputation: 703
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
}
}
}
Upvotes: 1
Reputation: 4924
This is my use case:
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
}
}
}
Upvotes: 4
Reputation: 165
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
}
}
}
}
Upvotes: 1
Reputation: 540055
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.
Upvotes: 63
Reputation: 69787
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"
}
}
}
Upvotes: 5
Reputation: 7584
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];
Upvotes: 2
Reputation: 14414
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"
}
}
}
Upvotes: 20
Reputation: 2300
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]
Upvotes: 14