10

I would like to associate multiple values with an enum value, in a generic way.

This can be done in Java:

enum Test {

    A("test", 2);

    final String var1;
    final int var2;

    Test (String var1, int var2) {
        this.var1 = var1;
        this.var2 = var2;
    }
}

 public static void main(String []args){
    Test test = Test.A;
    System.out.println(test.var1);
 }

But it looks like it's not possible with Swift? So far, according to docs, there are:

  1. Associated values. Example (from docs):

    enum Barcode {
        case UPCA(Int, Int, Int, Int)
        case QRCode(String)
    }
    

    But this is not what I need.

  2. Raw value. Example (from docs):

    enum ASCIIControlCharacter: Character {
        case Tab = "\t"
        case LineFeed = "\n"
        case CarriageReturn = "\r"
    }
    

    This would be what I need, but it can have only one value!

Is there an elegant solution for this...? Seems like a language design decision, as it would conflict with the associated values concept, at least in the current form. I know I could use e.g. a dictionary to map the enum values to the rest, but really missing to do this in one safe step, like in Java.

3 Answers 3

21

For Swift enum, you can only use (String|Integer|Float)LiteralConvertible types as the raw value. If you want to use existing type(e.g. CGPoint) for the raw value, you should follow @Alex answer.

I will provide 2 alternatives in this answer

Very simple solution

enum Test: String {
    case A = "foo:1"
    case B = "bar:2"

    var var1: String {
        return split(self.rawValue, { $0 == ":" })[0]
    }
    var var2: Int {
        return split(self.rawValue, { $0 == ":" })[1].toInt()!
    }
}

let test = Test.A
println(test.var1) // -> "foo"

You don't like this? go to next one :)

Behavior emulation using struct and static constants

struct Test {
    let var1: String
    let var2: Int
    private init(_ var1:String, _ var2:Int) {
        self.var1 = var1
        self.var2 = var2
    }
}

extension Test {
    static let A = Test("foo", 1)
    static let B = Test("bar", 2)
    static let allValues = [A, B]
}

let test = Test.A
println(test.var1) // -> "foo"

But of course, struct lacks some features from enum. You have to manually implement it.

Swift enum implicitly conforms Hashable protocol.

extension Test: Hashable {
    var hashValue:Int {
        return find(Test.allValues, self)!
    }
}

func ==(lhs:Test, rhs:Test) -> Bool {
    return lhs.var1 == rhs.var1 && lhs.var2 == rhs.var2
}

Test.A.hashValue // -> 0
Test.B.hashValue // -> 1
Test.A == Test.B // -> false

In the first code, we already have allValues that is corresponding to values() in Java. valueOf(...) in Java is equivalent to init?(rawValue:) in RawRepresentable protocol in Swift:

extension Test: RawRepresentable {

    typealias RawValue = (String, Int)

    init?(rawValue: RawValue) {
        self.init(rawValue)
        if find(Test.allValues, self) == nil{
            return nil
        }
    }

    var rawValue: RawValue {
        return (var1, var2)
    }
}

Test(rawValue: ("bar", 2)) == Test.B
Test(rawValue: ("bar", 4)) == nil

And so on...

I know this is not "in a generic way". And one thing we never can emulate is "Matching Enumeration Values with a Switch Statement" feature in Swift. you always need default case:

var test = Test.A
switch test {
case Test.A: println("is A")
case Test.B: println("is B")
default: fatalError("cannot be here!")
}
Sign up to request clarification or add additional context in comments.

2 Comments

If you often use var1, or var2, I recommend the latter. If not, the former would be sufficient.
I'm trying to do the first approach, but I can't get it to work. I believe this might have been changed in Swift 2.0? I get "Missing arguement for parameter 'isSeparator' in call" when I try, and I can't seem to fix it.
1

Yes it is a design decision but you can kind of work around it in some cases. The idea is to extend a Type to conform to one of: integer-literal­ floating-point-literal­ string-literal

The solution can be found here Bryan Chen's solution: How to create enum with raw type of CGPoint?

The second solution presented there by Sulthan may also be a way to go for you.

Comments

0

I'm not familiar enough with Swift's history to know if this was possible back when the question was asked. But this is what I would do today in Swift 5.x:

enum Direction {
    case north
    case east
    case south
    case west

    func name() -> String {
        switch self {
        case .north: return "North"
        case .east: return "East"
        case .south: return "South"
        case .west: return "West"
        }
    }

    func degress() -> Double {
        switch self {
        case .north: return 0.0
        case .east: return 90.0
        case .south: return 180.0
        case .west: return 270.0
        }
    }
}

It retains all the benefits of Swift enums, chief of all, IMO, the ability for the compiler to infer when your code is exhaustive when pattern matching.

1 Comment

Why -1. Need to provide reason. Is the answer wrong. Or missing some examples. Or not using the literal and number exactly. Wonder. For me I think swift 5 seems not giving much new answer cf one?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.