Swift enum both a string and an int - swift

I have a situation where I'm trying to do binary decoding of some data and the data types have both a numerical value and a string value and a name. I was thinking of using an enum such as:
enum TARGET_TRACK_TYPE : String {
case TT_INVALID = "Invalid"
case TT_TRUE_TRACK_ANGLE = "True Track Angle"
case TT_MAGNETIC = "Magnetic"
case TT_TRUE = "True"
}
However I also know that:
TT_INVALID = 0 and TT_TRUE_TRACK_ANGLE = 1, etc. Is there an easy way to encapsulate both these "things" the string and the numerical value into an enum construct or do i need to make some sort of struct/class to handle this?
I guess I'd like to do something like
let a = TARGET_TRACK_TYPE.rawValue(value: 2)
println(a)
which would print True Track Angle
Again, I know this can be done with a struct or a class but I'm specifically interested in the enum
Or for another example:
/// Emitter Category is defined in section 3.5.1.10 of the GDL90 Spec
struct EmitterCategory {
let category : Int
func getString() -> String {
switch(category) {
case 0:
return "No aircraft type information";
case 1:
return "Light";
case 2:
return "Smalle";
case 3:
return "Large";
case 4:
return "High Vortex Large";
case 5:
return "Heavy";
case 6:
return "Highly Manuverable";
case 7:
return "Rotorcraft";
case 8:
return "(Unassigned)";
case 9:
return "Glider/sailplane";
case 10:
return "Ligther than air";
case 11:
return "Parachutist/sky diver";
case 12:
return "Ultra light/hang glider/paraglider";
case 13:
return "(Unassigned)";
case 14:
return "Unmanned aerial vehicle";
case 15:
return "Space/transatmospheric vehicle";
case 16:
return "(Unassigned)";
case 17:
return "Surface vehicle - emergency vehicle";
case 18:
return "Surface vehicle - service vehicle";
case 19:
return "Point obstacle";
case 20:
return "Cluster Obstacle";
case 21:
return "Line Obstacle";
default:
return "(reserved)";
}
}
}
Is there a way to refactor this struct into an enum such that I construct the enum with an integer value but I "read" the enum as a string? I'm pretty sure the answer is no.

I think this will do it for me. Thank you self.. :)
protocol GDL90_Enum {
var description: String { get }
}
enum TARGET_ADDRESS_TYPE : Int, GDL90_Enum {
case ADSB_ICAO_ADDRESS = 0
case ADSB_SELF_ADDRESS = 1
case TISB_ICAO = 2
case TISB_TRACK_ID = 3
case SURFACE_VEHICLE = 4
case GROUND_STATION = 5
var description: String {
switch self {
case .ADSB_ICAO_ADDRESS:
return "ADS-B with ICAO address"
case .ADSB_SELF_ADDRESS:
return "ADS-B with Self-assigned address"
case .TISB_ICAO:
return "TIS-B with ICAO address"
case .TISB_TRACK_ID:
return "TIS-B with track file ID"
case .SURFACE_VEHICLE:
return "Surface Vehicle"
case .GROUND_STATION:
return "Ground Station Beacon"
default:
return "Reserved"
}
}
}

With Swift 4.2 this can be done using CaseIterable. One, relatively concise way is to do the following
enum Directions: String, CaseIterable {
case north, south, east, west
static var asArray: [Directions] {return self.allCases}
func asInt() -> Int {
return Directions.asArray.firstIndex(of: self)!
}
}
print(Directions.asArray[2])
// prints "east\n"
print(Directions.east.asInt())
// prints "2\n"
print(Directions.east.rawValue)
// prints "east\n"

You should use RawRepresentable
#objc enum AppEvent:Int, RawRepresentable {
case appLaunch
case homeScreen
public typealias RawValue = String
public var rawValue: RawValue {
switch self {
case .appLaunch : return "appLaunch"
case .homeScreen : return "homeScreen"
}
public init?(rawValue: RawValue) {
switch rawValue {
case "appLaunch" : self = .appLaunch
case "homeScreen" : self = .homeScreen
}

Have you considered using a dictionary?
let targetTrackDict: [Int: String] =
[99: "Invalid",
1: "True Track Angle",
2: "Magnetic",
5: "True"]
Note the number codes don't have to be ordered or contiguous. Being specific about the dictionary's type in its declaration prevents a lot of warnings or errors in the following snippets.
Getting the name for a code is easy:
var code = 2
if let name = targetTrackDict[code] {
print("\(name) has code \(code)")
} else {
print("\(code) is not a valid track type")
}
I haven't found a tidy way of getting the code for a name, but this does it:
let magneticCode = targetTrackDict.first(where:
{key, value in value == "Magnetic"})?.key
// returns an optional
and you would of course dress it up as a function. What you don't get automatically is an internal name for your track type, but do you need one? And the line above does it for you in a way.

Summing up #Jeef's answer and #NobodyNada's comment, here's a solution
public enum Animal: Int, CustomStringConvertible {
case Dog, Cat
public var description: String {
switch self.rawValue {
case 0: return "Dog"
case 1: return "Cat"
default: return ""
}
}
}
var animal = Animal.Dog
print(animal) // Dog

Related

Can I add enum value from another Swift enum?

I have enum with volume types and enum of drink types
enum AlcoholVolumeType: String {
case portion30
case portion40
case portion50
case wineglass100
case wineglass125
case wineglass150
case glass175
case glass200
case glass300
case beerBottle330
case beerBottle355
case beerGlass400
case britishBank440
case americanPint473
case beerBottle500
case britishPint568
case bomber650
And drink enum
enum AlcoholType: String {
case champagne
case wineRed
case wineWhite
case winePink
case portWine
static var wineTypes: [AlcoholType] { [.champagne, .wineRed, .wineWhite, .winePink, .portWine] }
var isWine: Bool { AlcoholType.wineTypes.contains(self) }
case beerLight
case beerDark
case beerUnfiltered
case cider
case ale
Also i have custom drink types, so i need to add static value to enum with volume types
I tried to do this but i have an error
enum AlcoholVolumeType: String {
case \(AlcoholType)wineglass150
// Consecutive declarations on a line must be separated by ';'
}
I will try to explain in more detail
The application has the function of adding your own alcohol
If we have two types of custom alcohol, and the same containers are selected, then a bug occurs with the statistics, since it is calculated by the type of container, and we have one container for two types of alcohol, the following two screenshots show this
That is, I need to create a container type for each new type of alcohol so that one is not used for all, and there is no such bug in the statistics
Maybe you are talking about enums with associated values?
Something like this:
enum Wine {
case red
case white
case pink
}
enum Beer {
case light
case dark
case unfiltered
}
enum AlcoholType {
case wine(type: Wine)
case beer(type: Beer)
}
let alcohol: AlcoholType = .wine(type: .red)
switch alcohol {
case .wine(type: let type):
switch type {
case .red:
break
case .white:
break
case .pink:
break
}
case .beer(type: let type):
switch type {
case .light:
break
case .dark:
break
case .unfiltered:
break
}
}
Its hard to decide what do you need. If you need to store drink type and volume in one place - may be would be better to use structures and enums with associated values:
enum Wine: String {
case red
case white
case pink
}
enum Beer: String {
case light
case dark
case unfiltered
}
enum AlcoholType {
case wine(type: Wine)
case beer(type: Beer)
func getName() -> String {
switch self {
case .wine(type: let type):
return "\(type.rawValue) wine"
case .beer(type: let type):
return "\(type.rawValue) beer"
}
}
enum AlcoholVolumeType {
case glass(Int)
case portion(Int)
case bottle(Int)
}
struct Drink {
let alcohol: AlcoholType
let volume: AlcoholVolumeType
}
if you need to check matching - you can extend this struct:
struct Drink {
let alcohol: AlcoholType
let volume: AlcoholVolumeType
init?(alcohol: AlcoholType, volume: AlcoholVolumeType) {
guard validCombination(of: alcohol, volume) else { return nil}
let alcohol = alcohol
let volume = volume
}
func validCombination(of alcohol: AlcoholType, _ volume: AlcoholVolumeType) -> Bool {
switch (alcohol, volume) {
case (.wine(_), .glass(let size):
return (size == 100) || (size == 125)
default:
return false
}
}
func getDescription() -> String {
return alcohol.getName()
}
}
let validCase = Drink?(alcohol: .wine(.red), volume: .glass(100))
let imposibleCase = Drink?(alcohol: .wine(.red), volume: .portion(30))
print(validCase)
print(imposibleCase)

Swift enum getting value of Int Enum using String value

I have an enum thats raw value is Int. I know How to get the raw value. But I am accessing the data through webservice and there data for Enum is coming in String value.
Let me show you my Enum, then we will discuss the data that need to be convert into Enum and then getting the raw value.
My Enum:
enum ReportStatus : Int {
case None = 0
case Received = 2
case Forward = 9
case Reporting = 14
case Completed = 50
}
and the data I am getting from webservice is sending me value in
string like so
var valueToCompareWithEnum = "Forward"
what I want:
I want to match that string value and to get the resultant int value.
For example as I am receiving "Forward" So I must convert it in enum
and getting the 9 Int value.
I know I can make switch case for it but I wanted to know if it can be handled with more clean way.....
Thanks in advance please help.
You can let your enum implement CaseIterable and then compare your string to the string representation of each enum case using Array.first(where:)
enum ReportStatus : Int, CaseIterable {
case None = 0
case Received = 2
case Forward = 9
case Reporting = 14
case Completed = 50
static func translate(_ string: String) -> Int? {
return ReportStatus.allCases.first(where: {string == "\($0)"})?.rawValue
}
}
As a variant here is a global generic function that does the same but it returns the enum case rather than the raw value.
func translate<T: CaseIterable>(_ string: String, forEnum: T.Type) -> T? {
return forEnum.allCases.first(where: {string == "\($0)"})
}
Usage
if let item = translate(valueToCompareWithEnum, forEnum: ReportStatus.self) {
print(item.rawValue)
}
You can code like this;
enum ReportStatus : Int {
case None = 0
case Received = 2
case Forward = 9
case Reporting = 14
case Completed = 50
static func getRawValue(from value : String) -> Int {
switch value {
case "Forward":
return self.Forward.rawValue
case "Received":
return self.Received.rawValue
case "Reporting":
return self.Reporting.rawValue
case "Completed":
return self.Completed.rawValue
default:
return self.None.rawValue
}
}
}
var valueToCompareWithEnum = "Forward"
let myVal = ReportStatus.getRawValue(from: valueToCompareWithEnum)
print(myVal) // 9
Since you're receiving these strings from a server, I assume you mean you're receiving them encoded in JSON. If so, then you likely want them to be Decodable. In that case, you can implement a custom decoder.
Before I show that, I'm going to rename your enum cases to match Swift style, which is a lowercase letter. I'm doing this to make the problem slightly harder and show more techniques you can use.
enum ReportStatus : Int {
case none = 0
case received = 2
case forward = 9
case reporting = 14
case completed = 50
}
Here's the most straightforward approach:
extension ReportStatus: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let stringValue = try container.decode(String.self)
switch stringValue {
case "None": self = .none
case "Received": self = .received
case "Forward": self = .forward
case "Reporting": self = .reporting
case "Completed": self = .completed
default:
throw DecodingError
.valueNotFound(Self.self,
.init(codingPath: decoder.codingPath,
debugDescription: "Unknown value for report status: \(stringValue)"))
}
}
}
This is a very good technique, but since our case names do match the server's keys closely (just not perfectly), we could fix that up by lowercasing the key:
enum ReportStatus : Int, CaseIterable { // Note CaseIterable
case none = 0
case received = 2
case forward = 9
case reporting = 14
case completed = 50
}
extension ReportStatus: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let stringValue = try container.decode(String.self)
guard let value = Self.allCases.first(where: { String(describing: $0) == stringValue.lowercased() })
else { throw DecodingError
.valueNotFound(Self.self,
.init(codingPath: decoder.codingPath,
debugDescription: "Unknown value for report status: \(stringValue)"))
}
self = value
}
}
I recommend you switch Int enum to String to make it easer to parse and add one more field to get int value for each case:
enum ReportStatus: String {
case none = "None"
case received = "Received"
case forward = "Forward"
case reporting = "Reporting"
case completed = "Completed"
var intValue: Int {
switch self {
case .none : return 0
case .received : return 2
case .forward : return 9
case .reporting : return 14
case .completed : return 50
}
}
}
let valueToCompareWithEnum = "Forward"
if let myVal = ReportStatus(rawValue: valueToCompareWithEnum) {
print(myVal.intValue) // 9
}
Because that data from web in String format it's kindly recommended parse it to String enum case and then use you'r own Int value where you need.

How can achieve Enum to have a arguments on a case

I have an enum which has a String raw value. I want one of the cases get a string as input and return a string which that input. How can I achieve this?
public enum PredictTypes: String {
case favtasks = "isFav == YES"
case importtanttasks = "isImportant == YES"
case alltasks = ""
case customList(listName: String) = "listName == \(listName)"
}
As I searched I found some posts but can't understand for my case:
Can associated values and raw values coexist in Swift enumeration?
I would rewrite your enum like this:
public enum PredictTypes {
case favtasks
case importanttasks
case alltasks
case customList(listName: String)
var rawValue: String {
switch self {
case .favtasks: return "isFav == YES"
case .importanttasks: return "isImportant == YES"
case .alltasks: return ""
case .customList(let listName): return "listName == \(listName)"
}
}
}

Can you initialize an Enum value from the name of its case (*not* its RawValue?)

Consider this enumeration (note its type is Int)
enum MyTestEnum : Int{
case one = 1
case eight = 8
case unknown = -1
}
You can easily initialize a version of this based on the raw value, like so...
let value = MyTestEnum(rawValue:-1)
I'm trying to find out if it can be initialized with a string representation of the case name itself (again, not the raw value, but the word after 'case') like so...
let value = MyTestEnum(caseName:"eight")
Note: I want this to work with any enum if possible, regardless of its raw value type. For instance, this one...
enum MyOtherEnum{
case xxx
case yyy
case zzz
}
let value = MyOtherEnum(caseName:"xxx")
So can this be done?
Thoughts:
I think Swift has a way to instantiate a class given a string representing the fully-qualified class-name. Perhaps something similar can be used here.
In Swift 4.2, this is quite easy to do now using CaseIterable.
enum MyOtherEnum: CaseIterable {
case xxx
case yyy
case zzz
init?(caseName: String) {
for value in MyOtherEnum.allCases where "\(value)" == caseName {
self = value
return
}
return nil
}
}
enum MyTestEnum: Int, CaseIterable{
case one = 1
case eight = 8
case unknown = -1
init?(caseName: String) {
for value in MyTestEnum.allCases where "\(value)" == caseName {
self = value
return
}
return nil
}
}
What I am doing here is creating a failable initializer which iterates through all potential cases, testing to see if "\(value)" (which returns the name for that potential case) matches the caseName argument passed in to the initializer.
When a match is found, self is set and the loop ends. Otherwise, nil is returned for the call.
Below, are two working and two failing examples:
let myOtherEnum = MyOtherEnum(caseName:"xxx")
print(myOtherEnum) // MyOtherEnum.xxx
let myTestEnum = MyTestEnum(caseName:"eight")
print(myTestEnum?.rawValue) // 8
let myOtherEnumFail = MyOtherEnum(caseName:"aaa")
print(myOtherEnumFail) // nil
let myTestEnumFail = MyTestEnum(caseName:"ten")
print(myTestEnumFail) // nil
Based on CodeBender's solution here is a nice extension for this case
extension CaseIterable {
///Note: case value and name can be different
init?(caseName: String) {
for v in Self.allCases where "\(v)" == caseName {
self = v
return
}
return nil
}
}
You can go with custom initializer
extension MyTestEnum {
public static func allValues() -> [MyTestEnum] {
let retVal = AnySequence { () -> AnyIterator<MyTestEnum> in
var raw = 0
return AnyIterator {
let current = withUnsafePointer(to: &raw) {
$0.withMemoryRebound(to: MyTestEnum.self, capacity: 1) { $0.pointee }
}
guard current.hashValue == raw else { return nil }
raw += 1
return current
}
}
return [MyTestEnum](retVal)
}
init?(caseName: String){
for v in MyTestEnum.allValues() {
if "\(v)" == caseName {
self = v
return
}
}
self = MyTestEnum.unknown
}
}
let test = MyTestEnum(caseName: "eight")
or simple manually all your case :)
extension MyTestEnum {
init?(caseName: String){
switch caseName {
case "eight": self.init(rawValue: 8)
case "one": self.init(rawValue: 1)
default: self.init(rawValue: -1)
}
}
}
let test1 = MyTestEnum(caseName: "eight")
let test2 = MyTestEnum(rawValue: 1)
Hope this helps !!
It sounds like your want your enum cases to have raw Int values and raw String values. It's not strictly possible to do this, but perhaps this comes close:
enum MyTestEnum : String {
case one
case eight
case unknown
var intValue: Int {
switch self {
case one: return 1
case eight: return 8
case unknown: return -1
}
}
}
Now you can initialize from case names, but you can also retrieve an intValue when needed. (If needed, you could easily add a failable initializer to allow initialization from integers as well.)

In Swift, is it possible to convert a string to an enum?

If I have an enum with the cases a,b,c,d is it possible for me to cast the string "a" as the enum?
Sure. Enums can have a raw value. To quote the docs:
Raw values can be strings, characters, or any of the integer or
floating-point number types
β€” Excerpt From: Apple Inc. β€œThe Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l,
So you can use code like this:
enum StringEnum: String
{
case one = "value one"
case two = "value two"
case three = "value three"
}
let anEnum = StringEnum(rawValue: "value one")!
print("anEnum = \"\(anEnum.rawValue)\"")
Note: You don't need to write = "one" etc. after each case. The default string values are the same as the case names so calling .rawValue will just return a string
EDIT
If you need the string value to contain things like spaces that are not valid as part of a case value then you need to explicitly set the string. So,
enum StringEnum: String
{
case one
case two
case three
}
let anEnum = StringEnum.one
print("anEnum = \"\(anEnum)\"")
gives
anEnum = "one"
But if you want case one to display "value one" you will need to provide the string values:
enum StringEnum: String
{
case one = "value one"
case two = "value two"
case three = "value three"
}
All you need is:
enum Foo: String {
case a, b, c, d
}
let a = Foo(rawValue: "a")
assert(a == Foo.a)
let πŸ’© = Foo(rawValue: "πŸ’©")
assert(πŸ’© == nil)
In Swift 4.2, the CaseIterable protocol can be used for an enum with rawValues, but the string should match against the enum case labels:
enum MyCode : String, CaseIterable {
case one = "uno"
case two = "dos"
case three = "tres"
static func withLabel(_ label: String) -> MyCode? {
return self.allCases.first{ "\($0)" == label }
}
}
usage:
print(MyCode.withLabel("one")) // Optional(MyCode.one)
print(MyCode(rawValue: "uno")) // Optional(MyCode.one)
In case with an enum with Int type you can do it so:
enum MenuItem: Int {
case One = 0, Two, Three, Four, Five //... as much as needs
static func enumFromString(string:String) -> MenuItem? {
var i = 0
while let item = MenuItem(rawValue: i) {
if String(item) == string { return item }
i += 1
}
return nil
}
}
And use:
let string = "Two"
if let item = MenuItem.enumFromString(string) {
//in this case item = 1
//your code
}
Riffing on djruss70's answer to create highly generalized solution:
extension CaseIterable {
static func from(string: String) -> Self? {
return Self.allCases.first { string == "\($0)" }
}
func toString() -> String { "\(self)" }
}
Usage:
enum Chassis: CaseIterable {
case pieridae, oovidae
}
let chassis: Chassis = Chassis.from(string: "oovidae")!
let string: String = chassis.toString()
Note: this will unfortunately not work if the enum is declared #objc. As far as I know as of Swift 5.3 there is no way to get this to work with #objc enum's except brute force solutions (a switch statement).
If someone happens to know of a way to make this work for #objc enums, I'd be very interested in the answer.
Swift 4.2:
public enum PaymentPlatform: String, CaseIterable {
case visa = "Visa card"
case masterCard = "Master card"
case cod = "Cod"
var nameEnum: String {
return Mirror(reflecting: self).children.first?.label ?? String(describing: self)
}
func byName(name: String) -> PaymentPlatform {
return PaymentPlatform.allCases.first(where: {$0.nameEnum.elementsEqual(name)}) ?? .cod
}
}
Extending Duncan C's answer
extension StringEnum: StringLiteralConvertible {
init(stringLiteral value: String){
self.init(rawValue: value)!
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
}
For Int enum and their String representation, I declare enum as follows:
enum OrderState: Int16, CustomStringConvertible {
case waiting = 1
case inKitchen = 2
case ready = 3
var description: String {
switch self {
case .waiting:
return "Waiting"
case .inKitchen:
return "InKitchen"
case .ready:
return "Ready"
}
}
static func initialize(stringValue: String)-> OrderState? {
switch stringValue {
case OrderState.waiting.description:
return OrderState.waiting
case OrderState.inKitchen.description:
return OrderState.inKitchen
case OrderState.ready.description:
return OrderState.ready
default:
return nil
}
}
}
Usage:
order.orderState = OrderState.waiting.rawValue
let orderState = OrderState.init(rawValue: order.orderState)
let orderStateStr = orderState?.description ?? ""
print("orderStateStr = \(orderStateStr)")
I used this:
public enum Currency: CaseIterable, Codable {
case AFN = 971 // Afghani (minor=2)
case DZD = 012 // Algerian Dinar (minor=2)
...
private static var cachedLookup: [String: Currency] = [:]
init?(string: String) {
if Self.cachedLookup.isEmpty {
Self.cachedLookup = Dictionary(uniqueKeysWithValues: Self.allCases.map { ("\($0)", $0) })
}
if let currency = Self.cachedLookup[string] {
self = currency
return
} else {
return nil
}
}
}
I found the other answers make this way more complicated then it needs to be. Here is a quick and concise example.
enum Gender: String {
case male, female, unspecified
}
Simple enum, note that I added ": String" to the enum itself to declare the type as string.
Now all you have to do is:
let example: Gender = Gender(rawValue: "male")
And thats it, 'example' is now an enum of type Gender with the value of .male
There is literally nothing else you need to do in Swift 4+.