How to print associated values in Swift enumerations? - swift

I'm looking for a way to print associated values of enumetations in Swift. ie. following code should print "ABCDEFG" for me but it doesn't.
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}
var productCode = Barcode.QRCode("ABCDEFG")
println(productCode)
// prints (Enum Value)
Reading the answers to this stackoverflow question, which is related to printing raw values of enumerations, I tried following code but it gives me an error
enum Barcode: String, Printable {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
var description: String {
switch self {
case let UPCA(int1, int2, int3, int4):
return "(\(int1), \(int2), \(int3), \(int4))"
case let QRCode(string):
return string
}
}
}
var productCode = Barcode.QRCode("ABCDEFG")
println(productCode)
// prints error: enum cases require explicit raw values when the raw type is not integer literal convertible
// case UPCA(Int, Int, Int, Int)
// ^
Since I'm new to Swift I can't understand what error message is about. Can someone know if it is possible or not.

The problem is that you added an explicit raw type to your Barcode enum—String. Declaring that it conforms to Printable is all you need:
enum Barcode: Printable {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
var description: String {
// ...
}
}
The compiler's complaint is that you didn't specify raw values with your non-integer raw value type, but you can't do that with associated values anyway. Raw string values, without associated types, could look like this:
enum CheckerColor: String, Printable {
case Red = "Red"
case Black = "Black"
var description: String {
return rawValue
}
}

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.

Return typed value from enum associated value

I have a enum like so:
enum MyEnum {
case a(Int)
case b(String)
case c(Int)
}
let arrayOfMyEnums: [MyEnum] = [ .a(4), .b("hello"), .c(42) ]
Now I'd like to write an extension that returns the type of the associated value.
private extension MyEnum {
// this does pseudo code
func value() -> ... // Should return typed associated value, so either Int or String
{
}
}
Wonder whether this can be expressed somehow with generics so I can call arrayOfMyEnums[0].value() and would get an Int, whereas arrayOfMyEnums[1].value() would return a string?

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

enum of non-literal values in 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.