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.

User
  • 31,811
  • 40
  • 131
  • 232

3 Answers3

19

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!")
}
rintaro
  • 51,423
  • 14
  • 131
  • 139
  • If you often use `var1`, or `var2`, I recommend the latter. If not, the former would be sufficient. – rintaro Feb 09 '15 at 05:00
  • 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. – ClockWise Nov 10 '15 at 14:23
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.

Community
  • 1
  • 1
Alex
  • 541
  • 5
  • 19
-1

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.

Jack Leow
  • 21,945
  • 4
  • 50
  • 55