58

There are examples on Swift book demonstrating associated values and raw values separately, is there a way to define enums with the two features together?

I have tried to combine them, but got errors:

enum Barcode :String {
    case UPCA(Int, Int, Int) = "Order 1" // Enum with raw type cannot have cases with arguments
    case QRCode(String) = "Order 2" // Enum with raw type cannot have cases with arguments
}
mfaani
  • 33,269
  • 19
  • 164
  • 293
vladof81
  • 26,121
  • 9
  • 38
  • 41
  • All the comments here provide workarounds that do work if your associated values are not being set dynamically during runtime, i.e. coming from API. In this case there is no way to do it, at least in Swift 2. – Michał Kreft Jun 29 '16 at 10:20

6 Answers6

68

Yes this is possible. An enum may contain both associated values and raw values. (Swift 5 & 4)

Shorthand notation

The issue with your code is that you are using the shorthand notation for RawRepresentable and defining associated types.

Let's look at how to define these separately:

1) RawRepresentable (shorthand notation):

enum Barcode: String {
    case UPCA   = "order 1"
    case QRCode = "order 2"
}

2) Associated types:

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

Each of these is great, but what if you need both as your code snippet shows.

Solution

Define the enum with associated values and then implement conformance to RawRepresentable separately in an extension (i.e. not using shorthand notation).

Example:

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

extension Barcode: RawRepresentable {

    public typealias RawValue = String

    /// Failable Initalizer
    public init?(rawValue: RawValue) {
        switch rawValue {
        case "Order 1":  self = .UPCA(1,1,1) 
        case "Order 2":  self = .QRCode("foo")
        default:
            return nil
        } 
    }

    /// Backing raw value
    public var rawValue: RawValue {
        switch self {
        case .UPCA:     return "Order 1"
        case .QRCode:   return "Order 2"
        }
    }

}

Minor Detail

In this solution, defaults for the associated values, e.g. .UPCA(1,1,1) must be supplied when constructing the enum from the rawValue argument. You can get fancy and use the associated types as part of the backing raw value — which is more powerful, but adds some complexity.

References

For more info on the topic see Ole Begemann's excellent write up.

cjmconie
  • 912
  • 7
  • 13
  • Technically this is all right, but only when .UPCA(1,1,1) , it got raw value "Order 1" , not any other like .UPAC(2,2,2). This maybe a little bit awkward. : ) – Neal.Marlin Jul 29 '22 at 09:48
14

The answers here are great, but don't provide an alternative, so here is one:

I'm trying to write a convenient wrapper for Parse.com's rest API, and honestly this restriction imposed by swift made me write a bit more code, but the end result is more readable:

class Parse {

    enum Endpoint {
        case signUp(ParseHTTPBody)
        case login(ParseHTTPBody)
    }

}

extension Parse.Endpoint {

    var httpMethod: String {
        switch self {
        case .signUp, .login:
            return "POST"
        }
    }

    var path: String {
        switch self {
        case .signUp:
            return "/1/users"
        case .login:
            return "/1/login"
        }
    }
}

Notice, now I httpMethod and path instead of rawValue, which is more readable in my case:

func setParseEndpoint(endpoint: Parse.Endpoint) -> Self {

    URL = NSURL(string: baseURL + endpoint.path)
    HTTPMethod = endpoint.httpMethod

    return self
}
Mazyod
  • 22,319
  • 10
  • 92
  • 157
5

As of Swift 3 you can have both in one enum.


Old answer:

The error messages seem pretty clear: You have to pick one or the other.

I don't know how it works behind the scenes, so this is a guess, but it seems likely that the case arguments are stored as a tuple value where the "Raw Type" value would otherwise be stored

Jiaaro
  • 74,485
  • 42
  • 169
  • 190
4

As @Jiaaro already pointed out, you cannot do that (including in Beta5).

However, it would make perfectly sense: an enum with attributed values could be implemented as a "discriminated union" or "variant" (see also wiki "tagged union"), where the "raw value" would take the role of the "tag".

That enum would then only take the space of the largest size of any attributed type plus the size of the tag (plus padding for alignment).

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
1

I solved it like this:

enum Barcode {

   case UPCA(Int, Int, Int)// = "Order 1"
   case QRCode(String)// = "Order 2"

   static func customRawValue(rawValue: String) -> Barcode? {

       switch rawValue {

       case "Order 1": return Barcode.UPCA(0, 0, 0)
       case "Order 2": return Barcode.QRCode("")
       default: return nil
       }
    }

    var customRawValue : String {

       switch self {
       case .UPCA: return "Order 1"
       case .QRCode: return "Order 2"
       }
    }
}

if let barcode = Barcode.customRawValue("Order 1") {

   print("Barcode found with custom rawValue: \(barcode)")

   print("Custom rawValue: \(barcode.customRawValue)")
}

It's somewhat hacky, but this solution worked great for my purpose!

fisher
  • 1,286
  • 16
  • 29
0

Elegant way to work with associated value ( even if the enum is indirect):

indirect enum MyEnum {
    var value: String? {
        return String(describing: self).components(separatedBy: "(").first
    }
    case greeting(text: String)
    case goodbye(bool: Bool)
    case hey
    case none
}

print(MyEnum.greeting(text: "Howdy").value)
// prints : greeting

now even you can use the value to implement Equatable like this:

    indirect enum MyEnum: Equatable {
     static func == (lhs: MyEnum, rhs: MyEnum) -> Bool {
        lhs.value == rhs.value
     }
    
     var value: String? {
        return String(describing: self).components(separatedBy: "(").first
     }
     case greeting(text: String)
     case goodbye(bool: Bool)
     case hey
     case none
   }
Mehrdad
  • 1,050
  • 1
  • 16
  • 27