Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enum of non-literal values in Swift

Tags:

enums

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: "")
}
like image 501
Mohsen Avatar asked Dec 07 '14 20:12

Mohsen


People also ask

Why Swift enums with associated values Cannot have a raw value?

We had to do this because Swift doesn't allow us to have both: raw values and associated values within the same enum. A Swift enum can either have raw values or associated values. Why is that? It's because of the definition of a raw value: A raw value is something that uniquely identifies a value of a particular type.

What is rawValue in enum?

If you define an enumeration with a raw-value type, the enumeration automatically receives an initializer that takes a value of the raw value's type (as a parameter called rawValue ) and returns either an enumeration case or nil . You can use this initializer to try to create a new instance of the enumeration.

Can enum conform to Swift protocol?

Yes, enums can conform protocols. You can use Swift's own protocols or custom protocols. By using protocols with Enums you can add more capabilities.

Can enums have variables Swift?

An enum is a special type of variable that is specifically used with switch and conditionals. Swift enumeration cases don't have unique default integer values (like an array), unlike languages such as TypeScript and Objective C where the first element has a value of 0 , the second a value of 1 , and so on.


2 Answers

@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/"
    }
}
like image 173
Stanislav Smida Avatar answered Oct 07 '22 19:10

Stanislav Smida


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
}
like image 33
Antonio Avatar answered Oct 07 '22 17:10

Antonio