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?
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
}
}
}
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.
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"
}
}
}
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]
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
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"
}
}
}
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
}
}
}
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];
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
}
}
}
}
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
}
}
}
Related
I have an enum used in class DoorModeModel:
enum DoorModeName: String {
case off = "MODE_OFF"
case exit = "MODE_EXIT"
case auto = "MODE_AUTO"
case partial = "MODE_PARTIAL"
case open = "MODE_OPEN"
}
Then there are two different targets in the app that use different descriptions. I put these in extensions based on target:
extension DoorModeModel.DoorModeName {
var presentableName: String {
switch self {
case .off: return "MODE_OFF".localized()
case .exit: return "MODE_EXIT".localized()
case .auto: return "MODE_AUTO".localized()
case .partial: return "MODE_PARTIAL".localized()
case .open: return "MODE_OPEN".localized()
}
}
}
Then the other extension:
extension DoorModeModel.DoorModeName {
var presentableName: String {
switch self {
case .off: return "MODE_OFF_2".localized()
case .exit: return "MODE_EXIT_2".localized()
case .auto: return "MODE_AUTO_2".localized()
case .partial: return "MODE_PARTIAL_2".localized()
case .open: return "MODE_OPEN_2".localized()
}
}
}
Works great. Then there is a similar, but slightly different enum used in another part of the app, DoorModeService:
enum OperatingModeName: Int {
case off = 0
case exit = 1
case auto = 2
case partial = 3
case open = 4
}
Like before, there are different descriptions based on the target. And like before I created extensions for them. The code is exactly the same as the two extensions above, save for extending a different enum:
extension DoorModeService.OperatingModeName {
var description: String {
switch self {
...
I don't want duplicate code, so I'm trying to find a way to use the same enum descriptions for both enums. I thought it would be possible to do this using protocol extensions but since the extensions aren't aware of the enums, I don't see how this would work:
protocol EnumDescription {
var description: String { get }
}
extension EnumDescription {
var description: String {
switch self {
case .off: return "MODE_OFF".localized()
case .exit: return "MODE_EXIT".localized()
case .auto: return "MODE_AUTO".localized()
case .partial: return "MODE_PARTIAL".localized()
case .open: return "MODE_OPEN".localized()
}
}
}
This gives me the error Cannot infer contextual base in reference to member 'x'
Do I set the extension to conform to enums, something like:
extension EnumDescription where self == enumSomething
Or how do I go about using the same descriptions for the two enums I have created?
You can create static property requirements with type Self for all common cases between the 2 enums on your protocol to be able to switch over them in the implementation of description.
protocol ModeName: CustomStringConvertible, Equatable {
static var off: Self { get }
static var exit: Self { get }
static var auto: Self { get }
static var partial: Self { get }
static var open: Self { get }
}
extension ModeName {
var description: String {
switch self {
case .off: return "MODE_OFF".localized()
case .exit: return "MODE_EXIT".localized()
case .auto: return "MODE_AUTO".localized()
case .partial: return "MODE_PARTIAL".localized()
case .open: return "MODE_OPEN".localized()
default:
return ""
}
}
}
enum DoorModeName: String, ModeName {
case off = "MODE_OFF"
case exit = "MODE_EXIT"
case auto = "MODE_AUTO"
case partial = "MODE_PARTIAL"
case open = "MODE_OPEN"
}
enum OperatingModeName: Int, ModeName {
case off = 0
case exit = 1
case auto = 2
case partial = 3
case open = 4
}
I am working on a school assignment for which I have to develop an iOS application in Swift 5. The application needs to utilize a web service (a web-API) and either file storage or user defaults.
I had chosen to develop a "QR code manager", in which users can create QR codes for a URL by setting a few design parameters, which are then sent to a generator API. This API (upon an OK request) returns an image in a specified format (PNG in my case).
I have a class with the URL and all the design properties of the QR code, which will also contain the image itself. Please see below code snippet for the class.
public class QRCode {
var bsId : Int?
var url : String?
var name: String?
var frame: Frame?
var logo: QrCodeLogo?
var marker: Marker?
var color : String?
var bgColor : String?
var image : Data?
init(data: [String:String]) {
self.url = data["url"]
self.frame = Frame.allCases.first(where: { $0.description == data["frame"] })
self.logo = QrCodeLogo.allCases.first(where: { $0.description == data["logo"] })
self.marker = Marker.allCases.first(where: { $0.description == data["marker"] })
self.bgColor = data["backGroundColor"]
self.color = data["colorLight"]
}
init(json: String) {
// todo
}
}
extension QRCode {
func toDict() -> [String:Any] {
var dict = [String:Any]();
let otherSelf = Mirror(reflecting: self);
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value;
}
}
return dict;
}
}
All the properties are nullable for ease of development, the class will be further refactored once I have successfully implemented everything.
I have tried various methods I found all over the internet, one of which can be found in the class extension. The function toDict() translates the object properties and their values to a Dictionary object of type [String:Any]. However, I read that when the Any datatype is encoded and then decoded, Swift cannot determine which complex datatype the decoded data is supposed to be, effectively rendering the data either meaningless or unusable.
Another method I found was through extending the Codable-protocol in the class. As far as I am aware however, Codable only accepts primitive datatypes.
Please find below my currently written code for file storage handling. It is not complete yet, but I felt it was a good start and might help in this question.
class StorageManager {
fileprivate let filemanager: FileManager = FileManager.default;
fileprivate func filePath(forKey key: String) -> URL? {
guard let docURL = filemanager.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first else {
return nil;
}
return docURL.appendingPathComponent(key);
}
func writeToStorage(identifier: String, data: QRCode) -> Void {
guard let path = filePath(forKey: identifier) else {
throw ApplicationErrors.runtimeError("Something went wrong writing the file to storage");
}
let dict = data.toDict();
// TODO:: Implement
}
func readFromStorage(identifier: String) -> Any {
// TODO:: Implement
return 0;
}
func readAllFromStorage() throws -> [URL] {
let docsURL = filemanager.urls(for: .documentDirectory, in: .userDomainMask)[0];
do {
let fileURLs = try filemanager.contentsOfDirectory(at: docsURL, includingPropertiesForKeys: nil);
return fileURLs;
} catch {
throw ApplicationErrors.runtimeError("Something went wrong retrieving the files from \(docsURL.path): \(error.localizedDescription)");
}
}
}
I am very new to Swift and I am running stuck on file storage. Is there any way I could store instances of this class in file storage in such a way that I could reïnstantiate this class when I retrieve the data?
Thanks in advance! Please do not hesitate to ask any questions if there are any.
Edit
Based on matt's comment, please find below the code snippets of the Marker, Frame, and QrCodeLogo enums.
The Frame enum:
public enum Frame: String, CaseIterable {
case noFrame
case bottomFrame
case bottomTooltip
case topHeader
static var count: Int { return 4 }
var description: String {
switch self {
case .noFrame:
return "no-frame"
case .bottomFrame:
return "bottom-frame"
case .bottomTooltip:
return "bottom-tooltip"
case .topHeader:
return "top-header"
}
}
}
The QrCodeLogo enum:
public enum QrCodeLogo: String, CaseIterable {
case noLogo
case scanMe
case scanMeSquare
static var count: Int { return 3 }
var description: String {
switch self {
case .noLogo:
return "no-logo"
case .scanMe:
return "scan-me"
case .scanMeSquare:
return "scan-me-square"
}
}
}
The Marker enum:
public enum Marker: String, CaseIterable {
case version1
case version2
case version3
case version4
case version5
case version6
case version7
case version8
case version9
case version10
case version11
case version12
case version13
case version15
case version16
static var count: Int { return 15 }
var description: String {
switch self {
case .version1:
return "version1"
case .version2:
return "version2"
case .version3:
return "version3"
case .version4:
return "version4"
case .version5:
return "version5"
case .version6:
return "version6"
case .version7:
return "version7"
case .version8:
return "version8"
case .version9:
return "version9"
case .version10:
return "version10"
case .version11:
return "version11"
case .version12:
return "version12"
case .version13:
return "version13"
case .version15:
return "version15"
case .version16:
return "version16"
}
}
}
All the above enums contain the valid design options for the API I use. They serve as an input restriction to prevent "invalid parameter"-errors from occurring.
Hopefully, this clears things up.
Thanks again!
A type can conform to Codable provided all its properties conform to Codable. All of your properties do conform to Codable except for the enums, and they will conform to Codable if you declare that they do. Thus, this simple sketch of your types compiles:
public enum Frame: String, Codable {
case noFrame
case bottomFrame
case bottomTooltip
case topHeader
}
public enum QrCodeLogo: String, Codable {
case noLogo
case scanMe
case scanMeSquare
}
public enum Marker: String, Codable {
case version1
case version2
case version3
case version4
case version5
case version6
case version7
case version8
case version9
case version10
case version11
case version12
case version13
case version15
case version16
}
public class QRCode : Codable {
var bsId : Int?
var url : String?
var name: String?
var frame: Frame?
var logo: QrCodeLogo?
var marker: Marker?
var color : String?
var bgColor : String?
var image : Data?
}
There are many, many other things about your code that could be improved.
You don't need CaseIterable or description for anything. Now that your type is Codable, you can use it to retrieve the values from JSON directly, automatically. If the names of your enum cases do not match the corresponding JSON keys, just make a CodingKey nested enum to act as a bridge.
In other words, being Codable makes your type both populatable directly from the JSON and serializable to disk.
I have an enum that worked fine in Xcode 11.3, Swift 5.1. I just graded to Xcode 11.4 Swift 5.2 and now I get a redeclaration error:
I did a global search and there aren't any other enums with the same name nor any other classes or enums that use the method. It never occurred prior to my upgrade. I did a clean, deep clean, and cleared out derived data.
How can I fix this error?
enum ActivityType: String {
case north
case south
case east
case west
case up
case down
case still
static func value(from raw: String?) -> ActivityType {
switch raw {
case ActivityType.north.rawValue():
return .north
case ActivityType.south.rawValue():
return .south
case ActivityType.east.rawValue():
return .east
case ActivityType.west.rawValue():
return .west
case ActivityType.up.rawValue():
return .up
case ActivityType.down.rawValue():
return .down
case ActivityType.still.rawValue():
return .still
default:
return .still
}
}
func rawValue() -> String { // error occurs here
switch self {
case .north:
return "north"
case .south:
return "south"
case .east:
return "east"
case .west:
return "west"
case .up:
return "up"
case .down:
return "down"
case .still:
return "still"
}
}
}
Since your enum already has String as the raw value type, you have an autogenerated rawValue property. It conflicts with your rawValue() function. However, it is unnecessary, since you can just use the autogenerated code:
enum ActivityType: String {
case north, south, east, west, up, down, still
static func value(from rawValue: String?) -> ActivityType {
guard let rawValue = rawValue, let activityType = ActivityType(rawValue: rawValue) else {
return .still
}
return activityType
}
}
The above does the same as your code. Of course even that is largely unnecessary, since you could just use ActivityType(rawValue: myString) ?? .still directly.
protocol RawRepresentableWithDefault: RawRepresentable {
static var `default`: Self { get }
}
extension RawRepresentableWithDefault {
init(rawValue: RawValue?) {
self = rawValue.flatMap(Self.init) ?? .default
}
}
enum ActivityType: String {
case north, south, east, west, up, down, still
}
extension ActivityType: RawRepresentableWithDefault {
static let `default` = still
}
I would like to do something like that:
public enum MyEnum: String {
case tapOnSection(section: String) = "tap on \(section)"
}
var section: String = "funny section"
func someFuntion() {
print(MyEnum.tapOnSection(self.section))
}
Console: "tap on funny section"
Do you some if could I do something like that or some suggestions?
Thank you
You can add methods to enums:
public enum MyEnum {
case tapOnSection(section: String)
var description: String {
switch self {
case let tapOnSection(section: section):
return "tap on \(section)"
}
}
}
var funnySection: String = "funny section"
print(MyEnum.tapOnSection(funnySection).description)
An enum can't have both associated values (i.e. cases with arguments) and raw values. That's a limitation of the language at the moment.
The simplest way I can think of to approximate what you want is to create a computed property that replaces the raw value in some way. e.g. for your specific case you can do this:
enum MyEnum
{
case tapOnSection(section: String)
}
// Might as well implement CustomStringConvertible so you can do
// print(MyEnum.something) instead of print(MyEnum.something.description)
extension MyEnum: CustomStringConvertible
{
var description: String
{
switch self
{
case .tapOnSection(let section):
return "tap on \(section)"
// A switch case for each case in your enum
}
}
}
print(MyEnum.tapOnSection(section: "foo")) // prints "tap on foo"
I'm trying to execute a function inside an enum, but when execute this code ContentType.SaveContent("News") i keep getting following error: Use of instance member on type 'ContentType'; did you mean to use a value of type 'ContentType' instead?. Why wont it run when i've set the type to String?
enum ContentType: String {
case News = "News"
case Card = "CardStack"
func SaveContent(type: String) {
switch type {
case ContentType.News.rawValue:
print("news")
case ContentType.Card.rawValue:
print("card")
default:
break
}
}
}
I would probably do this instead of what you are trying to do :
in ContentType Enum a func :
func saveContent() {
switch self {
case .News:
print("news")
case .Card:
print("cards")
}
}
in the other part of code that will use your enum :
func saveContentInClass(type: String) {
guard let contentType = ContentType(rawValue: type) else {
return
}
contentType.saveContent()
}
It's not a static func, therefore you can only apply it to an instance of the type, not to the type itself, which is what you are trying to do. Add static before func.
...and, just for good style, don't give funcs capital letters...