enum of non-literal values in Swift - swift

Is there any way to map a non-literal value like tuple of dictionary to enums? Following code will throw Raw value for enum must be literal.
enum FileType {
case VIDEO = ["name": "Video", "contentTypeMatcher": "video/"]
case IMAGE = ["name": "Image", "contentTypeMatcher": "image/"]
case AUDIO = ["name": "Audio", "contentTypeMatcher": "aduio/"]
case PDF = ["name": "PDF", "contentTypeMatcher":"application/pdf"]
case TEXT = ["name": "Text", "contentTypeMatcher": "text/"]
case FOLDER= ["name": "Folder", "contentTypeMatcher" :"application/x-directory"]
case PLAIN = ["name": "Plain", "contentTypeMatcher": ""]
}
It's the same when I use tuples:
enum FileType {
case VIDEO = (name: "Video", contentTypeMatcher: "video/")
case IMAGE = (name: "Image", contentTypeMatcher: "image/")
case AUDIO = (name: "Audio", contentTypeMatcher: "aduio/")
case PDF = (name: "PDF", contentTypeMatcher:"application/pdf")
case TEXT = (name: "Text", contentTypeMatcher: "text/")
case FOLDER = (name: "Folder", contentTypeMatcher :"application/x-directory")
case PLAIN = (name: "Plain", contentTypeMatcher: "")
}

#Antonio gives workaround but does not answer the actual question.
Define your enum.
enum FileType {
case Image, Video
}
Give cases non-literal values, whatever type you want with conforming to RawRepresentable protocol. Do it by enum extension to have cleaner code.
extension FileType: RawRepresentable {
typealias Tuple = (name: String, contentTypeMatcher: String)
private static let allCases = [FileType.Image, .Video]
// MARK: RawRepresentable
typealias RawValue = Tuple
init?(rawValue: Tuple) {
guard let c = { () -> FileType? in
for iCase in FileType.allCases {
if rawValue == iCase.rawValue {
return iCase
}
}
return nil
}() else { return nil }
self = c
}
var rawValue: Tuple {
switch self {
case .Image: return Tuple("Image", "image/")
case .Video: return Tuple("Video", "video/")
}
}
}
To be able to match Tuple in switch, implement pattern matching operator.
private func ~= (lhs: FileType.Tuple, rhs: FileType.Tuple) -> Bool {
return lhs.contentTypeMatcher == rhs.contentTypeMatcher && lhs.name == rhs.name
}
And thats it...
let a = FileType.Image
print(a.rawValue.name) // "Image"
let b = FileType(rawValue: a.rawValue)!
print(a == b) // "true"
print(b.rawValue.contentTypeMatcher) // "image/"
Let's say I answered the question without questioning. Now... Enums (in Swift at least) are designed to have unique cases. Caveat to this workaround is that you can (I hope by accident) hold same rawValue for more cases. Generally your example code smells to me. Unless you (for very reasonable reason) need to create new enum value from tuple, consider redesign. If you want go with this workaround, I suggest (depends on project) to implement some check if all case raw values are unique. If not, consider this:
enum FileType {
case Video, Image
var name: String {
switch self {
case .Image: return "Image"
case .Video: return "Video"
}
var contentTypeMatcher: String {
switch self {
case .Image: return "image/"
case .Video: return "video/"
}
}

The language reference, when talking about Enumeration Declaration, clearly states that:
the raw-value type must conform to the Equatable protocol and one of the following literal-convertible protocols: IntegerLiteralConvertible for integer literals, FloatingPointLiteralConvertible for floating-point literals, StringLiteralConvertible for string literals that contain any number of characters, and ExtendedGraphemeClusterLiteralConvertible for string literals that contain only a single character.
So nothing else but literals can be used as raw values.
A possible workaround is to represent the dictionary as a string - for example, you can separate elements with commas, and key from value with colon:
enum FileType : String {
case VIDEO = "name:Video,contentTypeMatcher:video/"
case IMAGE = "name:Image,contentTypeMatcher:image/"
...
}
Then, using a computed property (or a method if you prefer), reconstruct the dictionary:
var dictValue: [String : String] {
var dict = [String : String]()
var elements = self.rawValue.componentsSeparatedByString(",")
for element in elements {
var parts = element.componentsSeparatedByString(":")
if parts.count == 2 {
dict[parts[0]] = parts[1]
}
}
return dict
}

My coworkers and I have been debating this topic recently as Swifts enum type is unique from other languages. In a language like Java where an enum is just a class that inherits from Enumeration, you can have static non-literal values assigned to each case.
In swift, we can not find a supported way to do this. From Swift documentation:
If a value (known as a “raw” value) is provided for each enumeration case, the value can be a string, a character, or a value of any integer or floating-point type.
Alternatively, enumeration cases can specify associated values of any type to be stored along with each different case value, much as unions or variants do in other languages. You can define a common set of related cases as part of one enumeration, each of which has a different set of values of appropriate types associated with it.
The second paragraph may seem like it can do what #Antonio asked but it is not. In swift's example:
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
But each enum is an instance with different value types (tuple vs string) and the values within them are different based on each instance of the enum created.
I wanted something that allowed more than the limited raw values but each enum contained the same value type (ie tuple, object, etc...) and is static.
With my coworkers input we came up with two options that have different tradeoffs.
The first is a private static dictionary by the enum that holds the value type you desire:
enum FooBarDict {
case foo
case bar
private static let dict = [foo: (x: 42, y: "The answer to life, the universe, and everything"),
bar: (x: 420, y: "Party time")]
var x: Int? { return FooBarDict.dict[self]?.x }
var y: String? { return FooBarDict.dict[self]?.y }
}
Our issue with this implementation is that there's no way at compile time that you can ensure that the developer has exhaustively included all of the enum cases. This means that any properties you right must be optional or return a default.
To resolve that issue we came up with the following:
enum FooBarFunc {
case foo
case bar
typealias Values = (x: Int, y: String)
private func getValues() -> Values {
switch self {
case .foo: return (x: 42, y: "The answer to life, the universe, and everything")
case .bar: return (x: 420, y: "Party time")
}
}
var x: Int { return getValues().x }
var y: String { return getValues().y }
}
Now it is exhaustive due to the switch statement in the getValues! A developer can not add a new case and compile without explicitly adding the value type.
My (perhaps unfounded) fear with this approach is that it may be both slower due to the switch statement lookup - although this may be optimized to be as fast as the dictionary lookup. And I am unsure if it will create a new value each time a enum property is requested. I'm sure I could find answers to both of these concerns but I've already wasted too much time on it.
To be honest, I hope I'm just missing something about the language and this is easily done in another way.

Related

Swift equivalent of Java Enum.valueOf

I found two answered questions about how to get an enum constant's name as a String. I want to do the vice-versa, i.e. getting the enum constant with a given name in a String:
enum Unit: Int {
case SECOND
case MINUTE
case HOUR
static func valueOf(unit: String) -> Unit {
// ?
}
}
I want to keep the rawValue to be an Int.
Update: To make the intention clear, I want to persist an object that has a Unit property. I also want to display the unit chosen by the user in the UI, as a localized string. Therefore I need to assign a constant and unique value, the integer, and a String to each enum value.
There is no easy built-in conversion. You can make the enum iterable and iterate over the strings util you find the correct one:
enum Unit: Int, CaseIterable {
case SECOND
case MINUTE
case HOUR
static func valueOf(unit: String) -> Unit? {
for x in Unit.allCases {
if String(describing: x)==unit {
return x
}
}
return nil
}
}
You'd need to deal the the possibility that an input string might not mach any valid case-string. I used an optional, but you could as well throw an exception.
Several remarks:
This code is case sensitive. So "HOUR" will not lead to the same result as "hour". If needed, add a case normalisation.
This code is not optimal, since it will perform a lot of string conversions every iteration on every call. A better alternative would be to initialise a dictionary once, and use it subsequently
The latter could look like:
private static var ready = false
private static var strings = [String : Unit] ()
static func valueOf2(unit: String) -> Unit? {
if !ready {
for x in Unit.allCases {
strings [String(describing: x)]=x
}
ready = true
}
return strings[unit]
}
Swift has a CaseIterable protocol that you can use to find the case from its raw value.
enum Unit: Int, CaseIterable {
case second = 100
case minute = 200
case hour = 300
static func caseFrom(rawValue: Int) -> Unit? {
return allCases.first { $0.rawValue == rawValue }
}
}
if let unitCase = Unit.caseFrom(rawValue: 200) {
print(unitCase)
}
The premise of this enum is a bit confusing to me but this is what you're looking for.

Swift enum custom raw type not expressible by different literals

I was reading through the wonderful blog post by Jon Sundell where he is trying to demonstrate how one can use custom raw values with Swift Enums.
I had a play around with his code and made up a bit of a contrived example to see how robust the Enums with custom raw types are.
I wondered if I can make a type, instance of which can be used as a raw value whilst being expressible by both String and Integer literals. When I implemented ExpressiblebyIntegerLiteral the String part did not work the way I expected and I'm not quite sure why.
Here is the code (the question follows the code):
import Foundation
struct Path: Equatable {
var string: String
var int: Int
}
enum Target: Path {
case content
case resources
case images = "resources/images"
}
extension Path: ExpressibleByIntegerLiteral, ExpressibleByStringLiteral {
init(stringLiteral: String) {
string = stringLiteral
int = 0
}
init(integerLiteral: Int) {
string = ""
int = integerLiteral
}
}
if let stringTarget = Target(rawValue: "content") {
print("stringTarget: \(stringTarget)")
}
if let intTarget = Target(rawValue: 1) {
print("intTarget: \(intTarget)")
}
What I expected the above code to produce is both stringTarget and intTarget being initialized to appropriate Enum cases, however, the stringTarget one turns out to be nil.
If you remove the conformance to ExpressibleByIntegerLiteral protocol (and the appropriate block of code which initializes intTarget), the stringTarget automagically gets initialized as I expected: into Target.string case.
Could someone please explain to me what is going on here?
Solving your Question
What I expected the above code to produce is both stringTarget and intTarget being initialized to appropriate Enum cases, however, the stringTarget one turns out to be nil.
They aren't nil. They are this: ""
This happens because both the .content and .resources cases are not explicitly defined by a String. And because of this, they both take the ExpressibleByIntegerLiteral route, and are hence defined as this ""
init(integerLiteral: Int) {
string = "" // see
int = integerLiteral
}
Solved for Int
Use this fancy method in place of IntValue(rawValue: 1):
func IntValue(_ this: Int) -> String? {
return Target(rawValue: 0) != nil ? String(describing: Target(rawValue: 0)!) : nil
}
Solved for String
First, conform your enum to CaseIterable, like so:
enum Target: Path, CaseIterable {
Next, use this fancy method in place of Target(rawValue: "content"):
func StringValue(_ this: String) -> String? {
return Target.allCases.contains { this == String(describing: $0) } ? this : nil
}
Truly solved for String
Now, I've removed a crucial bug where case foo = "bar" can be found both as 'foo' or 'bar'. If you don't want this, use this code instead:
func StringValue(_ this: String) -> String? {
var found: String? = nil
_ = Target.allCases.filter {
if let a = Target(rawValue: Path.init(string: this, int: 0)) {
found = String(describing: a)
return this == String(describing: a)
}
found = String(describing: $0)
return this == String(describing: $0)
}
return found
}
Custom Raw Values for Enums
Here's a quick tutorial:
I am wondering if enum can conform it's rawValue to the ClosedRange struct, just like it can conform to String.
enum Foo: ClosedRange<Int> {
case bar = 1...4
}
Obviously this is not an option, since 1...4 is not a literal
This seems to work:
enum Foo: ClosedRange<Int> {
case foo = "1...3"
case bar = "1...4"
func overlaps(_ with: Foo) -> Bool { return self.rawValue.overlaps(with.rawValue) }
}
extension ClosedRange: ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public init(stringLiteral value: String) {
let v = value.split(separator: ".")
switch Bound.self {
case is Int.Type: self = (Int(v[0])! as! Bound)...(Int(v[1])! as! Bound)
default: fatalError()
}
}
}
It allows you to do this:
print(Foo.foo.overlaps(Foo.bar))
You can add more types like Double or String using this technique
Side Note: My attempt allows for non-unique rawValues (SR-13212) which is a shame. But I'm not thinking that is fixable:
enum Foo: ClosedRange<Int> {
case foo = "1...3"
case bar = "1...4"
case bar = "1...04" // Duplicate, but Swift allows it.
}

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

Enums and Generics in Swift

EDITED
Hello I trying to make my own Unit Converter
but there is some problem when i try to make both Weight and Length
There are so many duplicated codes
enum LengthUnit: String{
case inch
case cm
case m
case yard
static func unit(of value: String) -> LengthUnit?{
switch value {
case let value where value.contains("inch"):
return .inch
case let value where value.contains("cm"):
return .cm
case let value where value.contains("m"):
return .m
case let value where value.contains("yard"):
return .yard
default:
return nil
}
}
}
enum WeightUnit:String {
case g
case kg
case lb
case oz
static func unit(of value: String) -> WeightUnit?{
switch value {
case let value where value.contains("g"):
return .g
case let value where value.contains("kg"):
return .kg
case let value where value.contains("lb"):
return .lb
case let value where value.contains("oz"):
return .oz
default:
return nil
}
}
}
Not only get unit from String function but also many associated functions for converting, there are duplicated codes
So I try to implement it by generics but I don't have any idea of it
How can use enums and generics for both Unit types
Since you inherit your enums from String you're getting init?(rawValue: String) parsing initializer for free. Personally, I wouldn't create function like unit(of:) because it just throw away the amount part. Instead, I would create parse function like parse(value: String) -> (Double, LengthUnit)?
Anyway, if you really want unit(of:) function and want to reduce code duplication as much as possible you may indeed benefit from using generics.
First of all, we need Unit marker protocol like this
protocol UnitProtocol { }
Then, we can create generic function which will use init?(rawValue: String) of RawRepresentable Units to return unit based on passed string
func getUnit<U: UnitProtocol & RawRepresentable>(of value: String) -> U? where U.RawValue == String {
// you need better function to split amount and unit parts
// current allows expressions like "15.6.7.1cm"
// but that's question for another topic
let digitsAndDot = CharacterSet(charactersIn: "0123456789.")
let unitPart = String(value.drop(while: { digitsAndDot.contains($0.unicodeScalars.first!) }))
return U.init(rawValue: unitPart)
}
And thats essentially it. If you don't like to use functions and prefer static methods instead, then you just need to add these methods and call getUnit(of:) inside
enum LengthUnit: String, UnitProtocol {
case inch
case cm
case m
case yard
static func unit(of value: String) -> LengthUnit? {
return getUnit(of: value)
}
}
enum WeightUnit: String, UnitProtocol {
case g
case kg
case lb
case oz
static func unit(of value: String) -> WeightUnit? {
return getUnit(of: value)
}
}
Or, instead adding unit(of:) methods everywhere we may even do better and add extension
extension UnitProtocol where Self: RawRepresentable, Self.RawValue == String {
static func unit(of value: String) -> Self? {
return getUnit(of: value)
}
}
Now you'll get static unit(of:) for free by just adding conformance to String and Unit
enum WeightUnit: String, UnitProtocol {
case g
case kg
case lb
case oz
}
You can do this as a one-liner since the raw value of the enum item here is the string version of the enum item, so LengthUnit.inch --> "inch"
extension String {
var lengthUnit: LengthUnit? {
get {
return LengthUnit(rawValue:self)
}
}
}
Update
Updated version that contains an example of removing the number part of the string. What is the best solution for this is hard to know without knowing what kind of data to expect.
extension String {
var lengthUnit: LengthUnit? {
get {
let string = self.trimmingCharacters(in: CharacterSet(charactersIn: "01234567890."))
return LengthUnit(rawValue:string)
}
}
}
Comment: since you're creating a unit converter you are going to need to split your string into value and unit at some point anyway to be able to perform the conversion so it might be smarter to do that first and use my original version.
Comment 2: I don't see how you can possible make use of generics here, your input is always a String and I see no gain in having a function/property return a generics. You could simplify your design using only one Unit enum and then only having one unit property rather than two

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