How to get strings from enum - swift

I am trying to organize a bunch of string parameters in a enum. That way I can exclude the possibility of typos.
enum CompassPoint: String {
case n = "North"
case s = "South"
case e = "East"
case w = "West"
}
If I do it like this I need to use .rawValue to access the string. That is pretty ugly.
If I do it like this:
enum CompassPointAlt: String {
case n
case s
case e
case w
var str: String {
switch self {
case .n: return "North"
case .s: return "South"
case .e: return "East"
case .w: return "West"
}
}
}
I have to use the .str property to get the value. Which is visually more explicit, but the declaration is cumbersome.
There has to be a better way. Does anyone have a tipp for me?
Thanks

You could pull off some variation of the following:
import Foundation
enum Test: String, CustomStringConvertible {
case n = "North"
case s = "South"
case e = "East"
case w = "West"
var description: String {
return self.rawValue
}
}
print("Enum: \(Test.n)") // Prints: Enum: North
CustomStringConvertible lets you run the code in the description variable without explicitly referencing it when you want to convert to a string. If you dont need the enums to have custom associated values like Hi and Bye above and you are cool with having the name of the type be what prints, then you can get as small as this:
import Foundation
enum TestSmall {
case North
case South
}
print("Enum: \(TestSmall.North)") // prints: Enum: North

You can use static in combination with enum if you are mostly interested in using the enum for constants
enum CompassPoint {
static let n = "Nort"
static let s = "South"
static let e = "East"
static let w = "West"
}

Related

How can I do variants in Swift? [duplicate]

I would like to have a variable, which can have multiple types (only ones, I defined), like:
var example: String, Int = 0
example = "hi"
This variable should be able to hold only values of type Int and String.
Is this possible?
Thanks for your help ;)
An “enumeration with associated value” might be what you are looking for:
enum StringOrInt {
case string(String)
case int(Int)
}
You can either assign a string or an integer:
var value: StringOrInt
value = .string("Hello")
// ...
value = .int(123)
Retrieving the contents is done with a switch-statement:
switch value {
case .string(let s): print("String:", s)
case .int(let n): print("Int:", n)
}
If you declare conformance to the Equatable protocol then
you can also check values for equality:
enum StringOrInt: Equatable {
case string(String)
case int(Int)
}
let v = StringOrInt.string("Hi")
let w = StringOrInt.int(0)
if v == w { ... }
Here is how you can achieve it. Works exactly how you'd expect.
protocol StringOrInt { }
extension Int: StringOrInt { }
extension String: StringOrInt { }
var a: StringOrInt = "10"
a = 10 //> 10
a = "q" //> "q"
a = 0.8 //> Error
NB! I would not suggest you to use it in production code. It might be confusing for your teammates.
UPD: as #Martin R mentioned: Note that this restricts the possible types only “by convention.” Any module (or source file) can add a extension MyType: StringOrInt { } conformance.
No, this is not possible for classes, structs, etc.
But it is possible for protocols.
You can this:
protocol Walker {
func go()
}
protocol Sleeper {
func sleep()
}
var ab = Walker & Sleeper
or even
struct Person {
var name: String
}
var ab = Person & Walker & Sleeper
But I don't recomment use this way.
More useful this:
struct Person: Walker, Sleeper {
/// code
}
var ab = Person
You can use Tuple.
Example:
let example: (String, Int) = ("hi", 0)
And access each data by index:
let stringFromExampleTuple = example.0 // "hi"
let intFromtExampleTuple = example.1 // 0

Dynamically initialize enum based on associated value

This is my enum:
enum E {
case a(Int), b(String)
}
The enum's associated value types are unique and always exactly one.
Let's say I have this variable:
let myInt = 0
I want to create an instance of E, based on variable myInt, dynamically. This should result in:
E.a(0)
But in the 'real world', I don't know what property I get. I only know one thing: I can initialize enum E with it. I need to dynamically initialize the enum, based on a property value. I currently have a huge switch on the property to initialize the enum, I don't want that.
But I have no idea how to accomplish this task. I tried mirroring the enum type, but I get a complex type and I have no idea how to proceed initializing it even if I know the types.
So I get a property of a certain type. I know that certain type matches a case in enum E, because there is exactly one case which associated value corresponds to the property type. I want to initialize an instance of that enum with that case, with the value of the property.
If your only starting point is the type of what will eventually be an associated value, you can use a switch statement:
enum E {
case a(Int)
case b(String)
init(associatedValue: Any) {
switch associatedValue {
case is Int:
self = .a(associatedValue as! Int)
case is String:
self = .b(associatedValue as! String)
default:
fatalError("Unrecognized type!")
}
}
}
let response = E(associatedValue: 1) // .a(1)
let other = E(associatedValue: "haha!") // .b("haha!")
The problem here is this switch must be exhaustive, meaning cover all types. So you either need a dumping ground case (.unreachable(Any)) or a fatalError so you can catch these in development.
You can use a custom initializer: (I have used more descriptive names)
enum TypeFinder {
case int(Int)
case string(String)
case unknown(Any)
init(value: Any) {
switch value {
case let v as Int: self = .int(v)
case let v as String: self = .string(v)
default: self = .unknown(value)
}
}
}
Testing:
var unknownTypeValue: Any = "Testing.."
print(TypeFinder(value: unknownTypeValue))
unknownTypeValue = 1234
print(TypeFinder(value: unknownTypeValue))
unknownTypeValue = true
print(TypeFinder(value: unknownTypeValue))
I believe you can do something like that
enum E: ExpressibleByStringLiteral, ExpressibleByIntegerLiteral {
case a(Int), b(String)
init(stringLiteral value: StringLiteralType) {
self = .b(value)
}
init(integerLiteral value: IntegerLiteralType) {
self = .a(value)
}
}

concatenate nested enum string in swift

I want to declare an enum type (say, ParentEnum) which will be used as an argument of a function, it contains a number of "child" enums and each case of the child enum uses the name of the child enum as a prefix, here's an example:
enum ParentEnum: String {
enum ChildEnum1: String {
case c1 = "/ChildEnum1/c1"
case c2 = "/ChildEnum1/c2"
}
enum ChildEnum2: String {
case c1 = "/ChildEnum2/c1"
case c2 = "/ChildEnum2/c2"
}
...
}
Is there a way to generalize the "/ChildEnumX" part so I only need to define "/cX" as its rawValue, and when I call ParentEnum.ChildEnumX.c1, it will give me "/ChildEnumX/c1" like the example above.
I hope this makes sense, thanks in advance.
Just remembered to update this post in case someone else has the same problem, this is what I came up with:
enum ParentEnum {
case Child1(ChildEnum1)
case Child2(ChildEnum2)
enum ChildEnum1: String {
case c1 = "/c1"
case c2 = "/..."
}
enum ChildEnum2: String {
case c1 = "/..."
case c2 = "/..."
}
...
var rawValue: String {
switch self {
case .Child1(let child):
return "ChildEnum1/\(child.rawValue)"
case .Child2(let child):
return "ChildEnum2/\(child.rawValue)"
}
}
}
if you were using ParentEnum as an argument of a function, .Child1(.c1).rawValue would yield "ChildEnum1/c1". You could also override the rawValue of the Child enums to further extend the nesting levels. Hope this helps.

Swift 4 Decodable: How to map multiple values to a single enum value?

so I'm trying to use Decodable to decode a field into this format:
enum ClothingType: String, Decodable {
case SHIRT
case PANTS
case SHOES
case HAT
}
But my JSON returns the following values in the quotes, and I want them mapped to one of the four types above:
"T_SHIRT" and "LONG_SLEEVE_SHIRT" -> SHIRT
"JEANS" and "SHORTS" -> PANTS
"SNEAKERS" and "SANDALS" -> SHOES
"BASEBALL_CAP" and "WINTER_HAT" -> HAT
How do I achieve this with Decodable? Thanks!
I would instead recommend doing something like this:
enum ClothingType: String, Codable {
case tShirt = "T_SHIRT"
case longSleepShirt = "LONG_SLEEVE_SHIRT"
case jeans = "JEANS"
case shorts = "SHORTS"
case sneakers = "SNEAKERS"
case sandals = "SANDALS"
case baseballCap = "BASEBALL_CAP"
case winterHat = "WINTER_HAT"
var subType: SubType {
switch self {
case .tShirt, .longSleepShirt:
return .shirt
case .jeans, .shorts:
return .pants
case .sneakers, .sandals:
return .shoes
case .baseballCap, .winterHat:
return .hat
}
}
enum SubType {
case shirt
case pants
case shoes
case hat
}
}
It allows you to keep your encoded data structure true to how it will end up, while allowing you to keep naming conventions and define which aspects matter to you.
I learned this week that Decodable is really flexible, you can do something like:
enum ClothingType: Decodable {
case shirt
case pants
case shoes
case hat
private enum RawClothingType: String, Decodable {
case tShirt = "T_SHIRT"
case longSleepShirt = "LONG_SLEEVE_SHIRT"
case jeans = "JEANS"
case shorts = "SHORTS"
case sneakers = "SNEAKERS"
case sandals = "SANDALS"
case baseballCap = "BASEBALL_CAP"
case winterHat = "WINTER_HAT"
}
init(from decoder: Decoder) throws {
let rawClothingType = try RawClothingType(from: decoder)
switch rawClothingType {
case .tShirt, .longSleepShirt: self = .shirt
case .jeans, .shorts: self = .pants
case .sneakers, .sandals: self = .shoes
case .baseballCap, .winterHat: self = .hat
}
}
}
This hides the original clothing type (T-shirt, jeans. etc.) so use it if that's something you desire.

Accessing a String Enum by index

I have an enum in C and the index needs to be represented by a String.
How can a Swift enum of String type be used by integer index?
I would like to copy the enum to Swift, set the type to string and define all of the raw values to display text, and then use the C enum value to extract the raw value text for the Swift String enum.
Otherwise I will just create an array of strings.. But the enum would be more usable.
Swift 4.2 introduced CaseIterable which does exactly the same thing without the need to declare an allValues array. It works like this:
enum MyEnum: String, CaseIterable {
case foo = "fooString"
case bar = "barString"
case baz = "bazString"
}
and you can access its values by
MyEnum.allCases
or a value at a specific index by
MyEnum.allCases[index]
In Swift, enum types do not hold its index info of cases (at least, not provided for programmers).
So:
How can a Swift enum of String type be used by integer index?
The answer is "You cannot".
You can bind Int (or enum cases) and String values in many ways other than just create an array of strings..
For example, if your bound Strings can be the same as case labels, you can write something like this:
enum MyEnum: Int {
case foo
case bar
case baz
var string: String {
return String(self)
}
}
if let value = MyEnum(rawValue: 0) {
print(value.string) //->foo
}
If your Strings need to be a little more complex to display text, you can use Swift Dictionary to bind enum cases and Strings.
enum AnotherEnum: Int {
case foo
case bar
case baz
static let mapper: [AnotherEnum: String] = [
.foo: "FooString",
.bar: "BarString",
.baz: "BazString"
]
var string: String {
return AnotherEnum.mapper[self]!
}
}
if let value = AnotherEnum(rawValue: 1) {
print(value.string) //->BarString
}
A little bit more readable than a simple array of strings.
Simple workaround which is also useful if you want to enumerate a string enum.
enum MyEnum: String {
case foo = "fooString"
case bar = "barString"
case baz = "bazString"
static let allValues = [foo, bar, baz] //must maintain second copy of values
}
//enumeration advantage
for value in MyEnum.allValues {
print(value)
}
//get value by index
let value = MyEnum.allValues[1]
print(value) //barString
You can add an index as a part of the enum.
enum StringEnum: String, CaseIterable {
case pawn, rook, knight, bishop, king, queen
var name: String { self.rawValue.uppercased() }
var index: Int { StringEnum.allCases.firstIndex(of: self) ?? 0 }
}
And find enum cases by index with the function:
func findEnum(by index: Int) -> StringEnum? {
StringEnum.allCases.first(where: { $0.index == index })
}