How to enumerate an enum with String type?
Asked Answered
C

42

608
enum Suit: String {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"
}

For example, how can I do something like:

for suit in Suit {
    // do something with suit
    print(suit.rawValue)
}

Resulting example:

♠
♥
♦
♣
Chunk answered 3/6, 2014 at 5:3 Comment(7)
In what instance would you not know the type?Rufe
You are right, in this case it's String type.Chunk
No reflection in Swift yet...Headstrong
I found a safe way to do this using Swift's built in reflection. Although it technically isn't an enum, it can be made to act like one.Cipher
Isn't it ironic that they're called enumerations, but they are so painfully annoying to enumerate over in Swift?Metaphase
@CharltonProvatas If that were the only drawback in Swift, I'd call it a day. Looking at how many people offer different workarounds for this, I'm just gnawing my keyboard.Corrective
I have an answer you can refer to at the end of this thread. I think people will like it more than other solutions shown. https://mcmap.net/q/64169/-how-to-enumerate-an-enum-with-string-typeFactotum
M
351

Swift 4.2+

Starting with Swift 4.2 (with Xcode 10), just add protocol conformance to CaseIterable to benefit from allCases. To add this protocol conformance, you simply need to write somewhere:

extension Suit: CaseIterable {}

If the enum is your own, you may specify the conformance directly in the declaration:

enum Suit: String, CaseIterable { case spades = "♠"; case hearts = "♥"; case diamonds = "♦"; case clubs = "♣" }

Then the following code will print all possible values:

Suit.allCases.forEach {
    print($0.rawValue)
}

Compatibility with earlier Swift versions (3.x and 4.x)

If you need to support Swift 3.x or 4.0, you may mimic the Swift 4.2 implementation by adding the following code:

#if !swift(>=4.2)
public protocol CaseIterable {
    associatedtype AllCases: Collection where AllCases.Element == Self
    static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
    static var allCases: [Self] {
        return [Self](AnySequence { () -> AnyIterator<Self> in
            var raw = 0
            var first: Self?
            return AnyIterator {
                let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
                if raw == 0 {
                    first = current
                } else if current == first {
                    return nil
                }
                raw += 1
                return current
            }
        })
    }
}
#endif
Meridith answered 31/3, 2018 at 13:46 Comment(10)
@DmitryPetukhov I'd be happy to help, but: (1) are you sure you got the latest version of the code? (some crash was fixed a month ago) and (2) please give an MCVE of your custom type that can reproduce a crash, and your version of Xcode.Brecher
This works fine for me for debug builds, but as soon as I create a release and upload to TestFlight it crashes. Are Apple somehow stripping this out?Jeannettajeannette
@Meridith It's unnecessary in Xcode 10 surely as Swift 4.2 adds CaseIterable.Jeannettajeannette
@DanielWood I can't help with your TestFlight issue: it doesn't happen to me; make sure you're using the latest version of the code. As for Xcode 10, it can build three flavors of Swift: 3.2, 4.1 and 4.2, so this code is still a useful compatibility layer for Xcode 10 when using older Swift versions (for you or your dependencies).Brecher
It seems your version has one positive over the inbuilt version of CaseIterator. I can extend enums that are defined in another file with your version. If you make allCases in the extension public you can also extend enums defined in different frameworks.Lilylivered
I would love to hear an explanation of this code, I mean how did you come up with this?Whereby
@Whereby the protocol definition is straight from the link I gave. The implementation for older Swift versions is an adaptation by me partly from Kametrixom answer and partly others, plus a fix by Cat Jia for Xcode 10. As stated by Kametrixom and rintaro, it relies on undocumented (but open-source) underlying representation of an enum in Swift.Brecher
Why is it extension Suit: CaseIterable {} instead of enum Suit: CaseIterable {}? The swift book shows the latter (though the book as of now is Swift 5.2).Freeforall
@Freeforall I've updated the answer to clarify it. The Swift book and the Xcode 10 release notes mentions of CaseIterable are posterior to my answer and they used simplified examples where the enum isn't backed with String, as opposed to the present Stack Overflow question.Brecher
I would like to stress the importance of the "# if !swift (>=4.2)". If you wrote your code prior to swift 4.2 and you forgot to remove the code bellow "# if !swift (>=4.2)", when you use Xcode Version 11.4 to build locally on your test device, everything will be fine. But when your app is downloaded from app store or test flight, that piece of code will crash your app. That kind of bug is very hard to spot or debug.Product
A
541

This post is relevant here https://www.swift-studies.com/blog/2014/6/10/enumerating-enums-in-swift

Essentially the proposed solution is

enum ProductCategory : String {
     case Washers = "washers", Dryers = "dryers", Toasters = "toasters"

     static let allValues = [Washers, Dryers, Toasters]
}

for category in ProductCategory.allValues{
     //Do something
}
Angevin answered 10/6, 2014 at 9:21 Comment(8)
Nice, but...you have to enter your elements of the enumeration twice - once for the enumeration, once for the allValues. Not exactly as elegant as one would wish.Meta
Agree with the "but"... however as stated in the article perhaps there is an issue that an enum is really a set and therefore unordered... mind you... order cases defined in wouldn't be a bad start!Angevin
In Java the compiler does this for you, maybe Swift 2.0 will do this also. In particular in Java all enums get a description (toString in Java) method that gives the String as the case names (Washers, ...) and a Set of the cases is automatically created. Java also give you positional indexing. As I said, maybe Swift 2.0.Siriasis
Ideally you would have something similar to c# implementation in which you can do Enum.Values(typeof(FooEnum)) but exposed as an extension method (like map or reduce). FooEnum.values() :: values(EnumType -> [EnumType])Countenance
The article makes a good point right at the end about making each enum value is in the allValues array with a unit test. However, I can still see someone adding more elements, but not enforcing them in the unit test which still leaves us back at the beginning, not knowing for sure that all enum values are kept in allValues.Ramburt
Updates for Swift 3: use lowercase for enum cases, dot before each case in initializer. See github.com/apple/swift-evolution/blob/master/proposals/…Opinicus
Cleanest solution. I'd use the values name, like in Java. It's a shame it's not already OOTB. Could be easily added in the the following Swift version, along some breaking changes, as usual. ;)Archaean
I just added an alternative method to the end of this thread. It also provides multiple value types for one enum case. The way it is designed, if you make an error, the compiler will report the problem.Factotum
M
351

Swift 4.2+

Starting with Swift 4.2 (with Xcode 10), just add protocol conformance to CaseIterable to benefit from allCases. To add this protocol conformance, you simply need to write somewhere:

extension Suit: CaseIterable {}

If the enum is your own, you may specify the conformance directly in the declaration:

enum Suit: String, CaseIterable { case spades = "♠"; case hearts = "♥"; case diamonds = "♦"; case clubs = "♣" }

Then the following code will print all possible values:

Suit.allCases.forEach {
    print($0.rawValue)
}

Compatibility with earlier Swift versions (3.x and 4.x)

If you need to support Swift 3.x or 4.0, you may mimic the Swift 4.2 implementation by adding the following code:

#if !swift(>=4.2)
public protocol CaseIterable {
    associatedtype AllCases: Collection where AllCases.Element == Self
    static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
    static var allCases: [Self] {
        return [Self](AnySequence { () -> AnyIterator<Self> in
            var raw = 0
            var first: Self?
            return AnyIterator {
                let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
                if raw == 0 {
                    first = current
                } else if current == first {
                    return nil
                }
                raw += 1
                return current
            }
        })
    }
}
#endif
Meridith answered 31/3, 2018 at 13:46 Comment(10)
@DmitryPetukhov I'd be happy to help, but: (1) are you sure you got the latest version of the code? (some crash was fixed a month ago) and (2) please give an MCVE of your custom type that can reproduce a crash, and your version of Xcode.Brecher
This works fine for me for debug builds, but as soon as I create a release and upload to TestFlight it crashes. Are Apple somehow stripping this out?Jeannettajeannette
@Meridith It's unnecessary in Xcode 10 surely as Swift 4.2 adds CaseIterable.Jeannettajeannette
@DanielWood I can't help with your TestFlight issue: it doesn't happen to me; make sure you're using the latest version of the code. As for Xcode 10, it can build three flavors of Swift: 3.2, 4.1 and 4.2, so this code is still a useful compatibility layer for Xcode 10 when using older Swift versions (for you or your dependencies).Brecher
It seems your version has one positive over the inbuilt version of CaseIterator. I can extend enums that are defined in another file with your version. If you make allCases in the extension public you can also extend enums defined in different frameworks.Lilylivered
I would love to hear an explanation of this code, I mean how did you come up with this?Whereby
@Whereby the protocol definition is straight from the link I gave. The implementation for older Swift versions is an adaptation by me partly from Kametrixom answer and partly others, plus a fix by Cat Jia for Xcode 10. As stated by Kametrixom and rintaro, it relies on undocumented (but open-source) underlying representation of an enum in Swift.Brecher
Why is it extension Suit: CaseIterable {} instead of enum Suit: CaseIterable {}? The swift book shows the latter (though the book as of now is Swift 5.2).Freeforall
@Freeforall I've updated the answer to clarify it. The Swift book and the Xcode 10 release notes mentions of CaseIterable are posterior to my answer and they used simplified examples where the enum isn't backed with String, as opposed to the present Stack Overflow question.Brecher
I would like to stress the importance of the "# if !swift (>=4.2)". If you wrote your code prior to swift 4.2 and you forgot to remove the code bellow "# if !swift (>=4.2)", when you use Xcode Version 11.4 to build locally on your test device, everything will be fine. But when your app is downloaded from app store or test flight, that piece of code will crash your app. That kind of bug is very hard to spot or debug.Product
L
291

I made a utility function iterateEnum() for iterating cases for arbitrary enum types.

Here is the example usage:

enum Suit: String {
    case Spades = "♠"
    case Hearts = "♥"
    case Diamonds = "♦"
    case Clubs = "♣"
}

for f in iterateEnum(Suit) {
    println(f.rawValue)
}

Which outputs:

♠
♥
♦
♣

But, this is only for debug or test purposes: This relies on several undocumented Swift1.1 compiler behaviors, so, use it at your own risk.

Here is the code:

func iterateEnum<T: Hashable>(_: T.Type) -> GeneratorOf<T> {
    var cast: (Int -> T)!
    switch sizeof(T) {
        case 0: return GeneratorOf(GeneratorOfOne(unsafeBitCast((), T.self)))
        case 1: cast = { unsafeBitCast(UInt8(truncatingBitPattern: $0), T.self) }
        case 2: cast = { unsafeBitCast(UInt16(truncatingBitPattern: $0), T.self) }
        case 4: cast = { unsafeBitCast(UInt32(truncatingBitPattern: $0), T.self) }
        case 8: cast = { unsafeBitCast(UInt64($0), T.self) }
        default: fatalError("cannot be here")
    }

    var i = 0
    return GeneratorOf {
        let next = cast(i)
        return next.hashValue == i++ ? next : nil
    }
}

The underlying idea is:

  • Memory representation of enum, excluding enums with associated types, is just an index of cases when the count of the cases is 2...256, it's identical to UInt8, when 257...65536, it's UInt16 and so on. So, it can be unsafeBitcast from corresponding unsigned integer types.
  • .hashValue of enum values is the same as the index of the case.
  • .hashValue of enum values bitcasted from invalid index is 0.

Revised for Swift2 and implemented casting ideas from @Kametrixom's answer:

func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
    var i = 0
    return anyGenerator {
        let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
        return next.hashValue == i++ ? next : nil
    }
}

Revised for Swift3:

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafePointer(to: &i) {
            $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee }
        }
        if next.hashValue != i { return nil }
        i += 1
        return next
    }
}

Revised for Swift3.0.1:

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
        if next.hashValue != i { return nil }
        i += 1
        return next
    }
}
Like answered 5/2, 2015 at 10:14 Comment(12)
Awesome and the only answer which answers the question! But Yeah...not gonna touch it! +1 for effort, though!Blabber
I just posted my answer that works basically in the same way (only saw this answer later). It uses Swift 2.0 beta 6 and the modern features of the language.Congo
I used the Swift2 version and love it. This should be the top answer.Rett
Doesn't work with Swift 2.2: only outputs the last enum case.Tourane
Swift 3 version works well. Just needed to modify usage a bit: for f in iterateEnum(Suit.self) { print(f.rawValue) }That
+1 this is quite brilliant. It's also, IMHO, too clever to use, as evidenced by it breaking significantly in every major Swift version change. To the author's credit, the Swift 3 version was done a month before Swift 3 came out of beta... If you're going to take this answer and learn all this withUnsafePointer withMemoryRebound and pointee stuff, then use this by all means. Otherwise, I'd avoid it.Kreindler
Is it possible to return the iterator in makeIterator() method? That case, we can make the enum conform to Collection protocol. So we can use for in to iterate it.Leupold
I just want to add this is now broken in swift 4, but only on linux, so +1 to the above comments that this is too clever to use.Sympathy
Interesting problem that I ran into. If you have an Int enum that is marked as @objc, and try to iterate over the cases, it will freeze the app in release mode. Here is an example project demonstrating this problem. Any ideas what could be happening? Pure swift enums do not have this problem. github.com/n8tr/IteratingEnumTestProjectEvanevander
This solution was working for me in a debug environment, but when testing my release version it was broken suddenly :(Schnozzle
Thanks @rintaro. It works fine in Swift 4.X, considering the comment from @'Gene De Lisa': iterateEnum(Suit.self)Sure
What difference is there between withUnsafeBytes and withUnsafePointer? Both implementations seem to work for Swift 3.0 to Swift 4.1. Is one recommended?Brecher
H
134

The other solutions work but they all make assumptions of for example the number of possible ranks and suits, or what the first and last rank may be. True, the layout of a deck of cards probably isn't going to change much in the foreseeable future. In general, however, it's neater to write code which makes as little assumptions as possible. My solution:

I've added a raw type to the Suit enum, so I can use Suit(rawValue:) to access the Suit cases:

enum Suit: Int {
    case Spades = 1
    case Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
            case .Spades:
                return "spades"
            case .Hearts:
                return "hearts"
            case .Diamonds:
                return "diamonds"
            case .Clubs:
                return "clubs"
        }
    }
    func color() -> String {
        switch self {
        case .Spades:
            return "black"
        case .Clubs:
            return "black"
        case .Diamonds:
            return "red"
        case .Hearts:
            return "red"
        }
    }
}

enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
            case .Ace:
                return "ace"
            case .Jack:
                return "jack"
            case .Queen:
                return "queen"
            case .King:
                return "king"
            default:
                return String(self.rawValue)
        }
    }
}

Below the implementation of Card's createDeck() method. init(rawValue:) is a failable initializer and returns an optional. By unwrapping and checking its value in both while statements, there's no need to assume the number of Rank or Suit cases:

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
    func createDeck() -> [Card] {
        var n = 1
        var deck = [Card]()
        while let rank = Rank(rawValue: n) {
            var m = 1
            while let suit = Suit(rawValue: m) {
                deck.append(Card(rank: rank, suit: suit))
                m += 1
            }
            n += 1
        }
        return deck
    }
}

Here is how to call the createDeck method:

let card = Card(rank: Rank.Ace, suit: Suit.Clubs)
let deck = card.createDeck()
Henley answered 14/6, 2014 at 16:46 Comment(12)
Absolute BEST answer I've seen on various threads on this topic. Very elegant. This works with Int type enumerations, but I wonder how one might iterate through other types (string, custom types, etc.).Meta
One problem with this: I got a syntax error using the '+=' notation. "Playground execution failed: /var/folders/...expr.AEK8PX.swift:...: error: '[(Card)]' is not identical to 'UInt8' deck += Card(rank: rank, suit: suit) ^ " Using deck.append() instead of deck += worked fine. This was in Xcode 6b5 - perhaps a bug.Silesia
In Xcode 6 beta 5, some changes to operators in Swift were made: the += operator now only concatenates arrays, it does not append an element anymore. Instead the append method should be used. The answer should be edited to reflect this change.Henley
This is definitely the best solution. One thing to note. In the example in the book it does not have "case Spades = 1" as sdduursma has. I didn't catch this at first. that is one option, or you can just use "var m = 0"Faculty
Note that as of Xcode 6.1, fromRaw has changed so you'd do: let suit = Suit(rawValue:m)Soma
This makes the assumption that the raw values are sequential. When this is not true, for instance when the enum represents bit mask flags, the loop exits prematurely.Gooseberry
This solution assumes you can modify the definition of Suit. You can in this example, but the exercise was meant to get you to work with the enums you were given as if they were from an external source.Cloth
I am using Swift 2.2-dev on Linux, and I find that "m++" gives an obscure syntax error, while "m += 1" works.Pudens
The only thing I want to complain about is that I can't call it as a static method, but have to create a card object first.Akanke
I would declare the createDeck a static. That way one woudn't have to create a card to withdraw a deck. @Akanke kind of suggested it.Encrust
Agreed that you should have the card instance creating the deck. That seems backward. createDeck() should be static or part of a deck object.Tonsillotomy
@Akanke are you saying you can't call createDeck() as a static method because it's not possible, or because the presented solution doesn't present it that way? certainly the former is possible, and I agree with other commenters that it's preferable.Nutwood
C
77

I stumbled around in the bits and bytes and created an extension that I later found out works very similar to @rintaro's answer. It's used like this:

enum E : EnumCollection {
    case A, B, C
}

Array(E.cases())    // [A, B, C]

What's remarkable is that it's usable on any enum without associated values. Note that this doesn't work for enums that have no cases.

As with @rintaro's answer, this code uses the underlying representation of an enum. This representation isn't documented and might change in the future, which would break it. I don't recommend the usage of this in production.

Code (Swift 2.2, Xcode 7.3.1, not working on Xcode 10):

protocol EnumCollection : Hashable {}
extension EnumCollection {
    static func cases() -> AnySequence<Self> {
        typealias S = Self
        return AnySequence { () -> AnyGenerator<S> in
            var raw = 0
            return AnyGenerator {
                let current : Self = withUnsafePointer(&raw) { UnsafePointer($0).memory }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }
    }
}

Code (Swift 3, Xcode 8.1, not working on Xcode 10):

protocol EnumCollection : Hashable {}
extension EnumCollection {
    static func cases() -> AnySequence<Self> {
        typealias S = Self
        return AnySequence { () -> AnyIterator<S> in
            var raw = 0
            return AnyIterator {
                let current : Self = withUnsafePointer(to: &raw) { $0.withMemoryRebound(to: S.self, capacity: 1) { $0.pointee } }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }
    }
}

I have no idea why I need typealias, but the compiler complains without it.

Congo answered 6/9, 2015 at 23:4 Comment(17)
This answer is even better than my answer, especially in casting part :)Like
But I think this works only on little endian environment?Like
@Like Hmm I don't know, I mean on big endians, everything should be reversed I think, not sure... How can I test this? Are there even big endians nowadays though?Congo
No, there is no big endian env, currently. Let's see someone would port Swift after it's opensourced.Like
@Kametrixom: This answer is valid far as the Apple ecosystem is concerned.Anomalism
@Congo I have an enum called EventType. When I call EventType.cases(), the return type is EnumSequence<EventType>. How can I make it [EventType]?Quita
@AbbeyJackson Everything except the protocol extension on Hashable works for me, I'll update the answerCongo
strange, I can't get it to compile in playgroundAdaurd
@ConfusedVorlon I made vast simplifications and updated to the latest Swift versions, thanks for mentioning it ;)Congo
I think i can't thank you enough for this answer @Congo It's been a while but if you are still looking about the typealias reason, take a look in the AnySequence Apple's declaration SequenceType and Element doesn't have to be the same type, so in the init have to specify the S type using that typealiasSolder
Please look at my answer as well and see if returning an Array is a better idea than AnySequence https://mcmap.net/q/64169/-how-to-enumerate-an-enum-with-string-typeSnocat
Xcode 8 beta 6 has changed this again! I get the following error ` 'init' is unavailable: use 'withMemoryRebound(to:capacity:_)' to temporarily view memory as another layout-compatible type.`Twelfth
@ConfusedVorlon: see answer above by @Rintaro: replace withUnsafePointerpointee} by withUnsafePointer(to: &i) { $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } }Wove
Would it not make sense to make it into a computed property? static var cases: AnySequence<Self>Gayn
This seems to be no longer working as of Xcode 10. (I know this would not be required with Swift 4.2) but when using Swift 4(.1) in Xcode 10 this code is no longer working (raw value is unequal)Renfro
The core of XCode 10 problem in your solution is hashValue, and this solution does not use it: https://mcmap.net/q/64169/-how-to-enumerate-an-enum-with-string-type You can reuse 'return AnyIterator' block in your implementation.Snick
@SergeyTeryokhin to be fair, the linked solution without hashValue is from someone's edit. Similar corrections could be applied to many posts... but anyway, we should all be using Swift 4.2 already.Brecher
M
26

You could iterate through an enum by implementing the ForwardIndexType protocol.

The ForwardIndexType protocol requires you to define a successor() function to step through the elements.

enum Rank: Int, ForwardIndexType {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King

    // ... other functions

    // Option 1 - Figure it out by hand
    func successor() -> Rank {
        switch self {
            case .Ace:
              return .Two
            case .Two:
              return .Three

            // ... etc.

            default:
              return .King
        }
    }

    // Option 2 - Define an operator!
    func successor() -> Rank {
        return self + 1
    }
}

// NOTE: The operator is defined OUTSIDE the class
func + (left: Rank, right: Int) -> Rank {
    // I'm using to/from raw here, but again, you can use a case statement
    // or whatever else you can think of

    return left == .King ? .King : Rank(rawValue: left.rawValue + right)!
}

Iterating over an open or closed range (..< or ...) will internally call the successor() function which allows you to write this:

// Under the covers, successor(Rank.King) and successor(Rank.Ace) are called to establish limits
for r in Rank.Ace...Rank.King {
    // Do something useful
}
Millardmillboard answered 16/7, 2014 at 22:27 Comment(6)
I find this to be the most "proper" answer to the question, even the most "elegant" (extra code necessary vis-a-vis other options here withstanding) given the resulting syntax when used within a range (the syntax is what I would expect to be able to do if enums where enumeratable sans workarounds). Thanks! Although it is worth noting that if the operator overloading is employed elsewhere besides successor() (which seems tempting) then obviously the forced unwrapping is dangerous. Also, the infix seems unnecessary...?Zloty
Updated answer to reflect latest Swift language specificationsMillardmillboard
A properly defined successor() method (first option) would eliminate the need for the enum to have an associated type. +1Pragmatism
But this elegant answer won't work for String enums would it?Tip
Most "proper" / best practice solution! +1-edStimulative
ForwardIndexType doesn't exist in swift 3; probably we can implement something similar with IteratorProtocolSholokhov
E
19

Updated Code : Swift 4.2/Swift 5

enum Suit: String, CaseIterable {
   case spades = "♠"
   case hearts = "♥"
   case diamonds = "♦"
   case clubs = "♣"
}

To access the Output as per question:

for suitKey in Suit.allCases {
    print(suitKey.rawValue)
}

Output :

♠
♥
♦
♣

CaseIterable: provides a collection of all of its values. Types that conform to the CaseIterable protocol are typically enumerations without associated values. When using a CaseIterable type, you can access a collection of all of the type’s cases by using the type’s allCases property.

For accessing cases we are using .allCases. For more information click https://developer.apple.com/documentation/swift/caseiterable

Entoderm answered 20/8, 2020 at 9:2 Comment(0)
R
18

This problem is now much easier. Here is my Swift 4.2 Solution:

enum Suit: Int, CaseIterable {
  case None
  case Spade, Heart, Diamond, Club

  static let allNonNullCases = Suit.allCases[Spade.rawValue...]
}

enum Rank: Int, CaseIterable {
  case Joker
  case Two, Three, Four, Five, Six, Seven, Eight
  case Nine, Ten, Jack, Queen, King, Ace

  static let allNonNullCases = Rank.allCases[Two.rawValue...]
}

func makeDeck(withJoker: Bool = false) -> [Card] {
  var deck = [Card]()
  for suit in Suit.allNonNullCases {
    for rank in Rank.allNonNullCases {
      deck.append(Card(suit: suit, rank: rank))
    }
  }
  if withJoker {
    deck.append(Card(suit: .None, rank: .Joker))
  }
  return deck
}

Pre 4.2:

I like this solution which I put together after finding "List comprehension in Swift".

It uses Int raws instead of Strings but it avoids typing twice, it allows customizing the ranges, and doesn't hard code raw values.

This is a Swift 4 version of my original solution but see the 4.2 improvement above:

enum Suit: Int {
  case None
  case Spade, Heart, Diamond, Club

  static let allRawValues = Suit.Spade.rawValue...Suit.Club.rawValue
  static let allCases = Array(allRawValues.map{ Suit(rawValue: $0)! })
}
enum Rank: Int {
  case Joker
  case Two, Three, Four, Five, Six
  case Seven, Eight, Nine, Ten
  case Jack, Queen, King, Ace

  static let allRawValues = Rank.Two.rawValue...Rank.Ace.rawValue
  static let allCases = Array(allRawValues.map{ Rank(rawValue: $0)! })
}
func makeDeck(withJoker: Bool = false) -> [Card] {
  var deck = [Card]()
  for suit in Suit.allCases {
    for rank in Rank.allCases {
      deck.append(Card(suit: suit, rank: rank))
    }
  }
  if withJoker {
    deck.append(Card(suit: .None, rank: .Joker))
  }
  return deck
}
Revet answered 6/12, 2015 at 12:3 Comment(3)
Oh, I see now that mine is basically the same as Sutean Rutjanalard's.Revet
Actually, I liked your implementation better. I think it is clearer! 1 upvote. Actually the most voted answers are too clever and will most certainly break in the future. Yours promises some stability in the future.Inheritor
You could modernize allCases as: static let allCases = allRawValues.compactMap { Rank(rawValue: $0) }Gatling
L
17

In principle it is possible to do it this way assuming that you don't use raw values assignment for enum's cases:

enum RankEnum: Int {
  case Ace
  case One
  case Two
}

class RankEnumGenerator: Generator {
    var i = 0
    typealias Element = RankEnum
    func next() -> Element? {
        let r = RankEnum.fromRaw(i)
        i += 1
        return r
    }
}

extension RankEnum {
    static func enumerate() -> SequenceOf<RankEnum> {
        return SequenceOf<RankEnum>({ RankEnumGenerator() })
    }
}

for r in RankEnum.enumerate() {
    println("\(r.toRaw())")
}
Loree answered 3/6, 2014 at 22:2 Comment(6)
This is nice but it only works for continuous Integer enums starting at 0Merchant
@Robert, as my comment states above: "you don't use raw values assignment for enum's cases"Loree
Yes - don't use raw values, as well as setting the underlying type to be int. In swift you don't need a type for an enum like in the suits example. enum ItWontWorkForThisEnum {case a, b, c}Merchant
How would this address the issue if a tuple is associated with the enumeration case?Angevin
You can't associate a tuple to an enum very easily.Pragmatism
Should updated answer for XCode 6.1... Generator should be changed to GeneratorType and RankEnum.fromRaw(i) should be RankEnum(rawValue: i).Fingerstall
W
16

If you give the enum a raw Int value it will make looping much easier.

For example, you can use anyGenerator to get a generator that can enumerate across your values:

enum Suit: Int, CustomStringConvertible {
    case Spades, Hearts, Diamonds, Clubs
    var description: String {
        switch self {
        case .Spades:   return "Spades"
        case .Hearts:   return "Hearts"
        case .Diamonds: return "Diamonds"
        case .Clubs:    return "Clubs"
        }
    }
    static func enumerate() -> AnyGenerator<Suit> {
        var nextIndex = Spades.rawValue
        return anyGenerator { Suit(rawValue: nextIndex++) }
    }
}
// You can now use it like this:
for suit in Suit.enumerate() {
    suit.description
}
// or like this:
let allSuits: [Suit] = Array(Suit.enumerate())

However, this looks like a fairly common pattern, wouldn't it be nice if we could make any enum type enumerable by simply conforming to a protocol? Well with Swift 2.0 and protocol extensions, now we can!

Simply add this to your project:

protocol EnumerableEnum {
    init?(rawValue: Int)
    static func firstValue() -> Int
}
extension EnumerableEnum {
    static func enumerate() -> AnyGenerator<Self> {
        var nextIndex = firstRawValue()
        return anyGenerator { Self(rawValue: nextIndex++) }
    }
    static func firstRawValue() -> Int { return 0 }
}

Now any time you create an enum (so long as it has an Int raw value), you can make it enumerable by conforming to the protocol:

enum Rank: Int, EnumerableEnum {
    case Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King
}
// ...
for rank in Rank.enumerate() { ... }

If your enum values don't start with 0 (the default), override the firstRawValue method:

enum DeckColor: Int, EnumerableEnum {
    case Red = 10, Blue, Black
    static func firstRawValue() -> Int { return Red.rawValue }
}
// ...
let colors = Array(DeckColor.enumerate())

The final Suit class, including replacing simpleDescription with the more standard CustomStringConvertible protocol, will look like this:

enum Suit: Int, CustomStringConvertible, EnumerableEnum {
    case Spades, Hearts, Diamonds, Clubs
    var description: String {
        switch self {
        case .Spades:   return "Spades"
        case .Hearts:   return "Hearts"
        case .Diamonds: return "Diamonds"
        case .Clubs:    return "Clubs"
        }
    }
}
// ...
for suit in Suit.enumerate() {
    print(suit.description)
}

Swift 3 syntax:

protocol EnumerableEnum {
    init?(rawValue: Int)
    static func firstRawValue() -> Int
}

extension EnumerableEnum {
    static func enumerate() -> AnyIterator<Self> {
        var nextIndex = firstRawValue()

        let iterator: AnyIterator<Self> = AnyIterator {
            defer { nextIndex = nextIndex + 1 }
            return Self(rawValue: nextIndex)
        }

        return iterator
    }

    static func firstRawValue() -> Int {
        return 0
    }
}
Wilmawilmar answered 5/9, 2015 at 23:8 Comment(2)
nextIndex++ will be removed in swift 3. What do you suggest as replacement to - var nextIndex = firstRawValue() return anyGenerator { Self(rawValue: nextIndex++) }Winebibber
Figured it out. defer { nextIndex += 1 } return AnyGenerator { Self(rawValue: nextIndex) }Winebibber
F
12

Updated to Swift 2.2+

func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
    var i = 0
    return AnyGenerator {
        let next = withUnsafePointer(&i) {
            UnsafePointer<T>($0).memory
        }
        if next.hashValue == i {
            i += 1
            return next
        } else {
            return nil
        }
    }
}

It's updated code to Swift 2.2 form @Kametrixom's answer

For Swift 3.0+ (many thanks to @Philip)

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafePointer(&i) {
            UnsafePointer<T>($0).pointee
        }
        if next.hashValue == i {
            i += 1
            return next
        } else {
            return nil
        }
    }
}
Foil answered 23/3, 2016 at 12:8 Comment(3)
@Tourane could you please explain what do you mean?Foil
Oops, sorry, I rechecked and there was a playground bug: in real project this code works as expected, thanks! =)Tourane
Great solution! Also, works fine on Swift 3 with minimal changes ('AnyGenerator' renamed 'AnyIterator' and '.memory' renamed to '.pointee').Triboluminescence
F
12

Swift 5 Solution:

enum Suit: String, CaseIterable {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"
}

// access cases like this:

for suitKey in Suit.allCases {
    print(suitKey)
}
Fermium answered 18/4, 2019 at 10:3 Comment(0)
F
10

Xcode 10 with Swift 4.2

enum Filter: String, CaseIterable {

    case salary = "Salary"
    case experience = "Experience"
    case technology = "Technology"
    case unutilized = "Unutilized"
    case unutilizedHV = "Unutilized High Value"

    static let allValues = Filter.allCases.map { $0.rawValue }
}

Call it

print(Filter.allValues)

Prints:

["Salary", "Experience", "Technology", "Unutilized", "Unutilized High Value"]


Older versions

For enum representing Int

enum Filter: Int {
    case salary
    case experience
    case technology
    case unutilized
    case unutilizedHV
    
    static let allRawValues = salary.rawValue...unutilizedHV.rawValue  // First to last case
    static let allValues = allRawValues.map { Filter(rawValue: $0)!.rawValue }
}

Call it like this:

print(Filter.allValues)

Prints:

[0, 1, 2, 3, 4]


For enum representing String

enum Filter: Int {
    case salary
    case experience
    case technology
    case unutilized
    case unutilizedHV
    
    static let allRawValues = salary.rawValue...unutilizedHV.rawValue  // First to last case
    static let allValues = allRawValues.map { Filter(rawValue: $0)!.description }
}

extension Filter: CustomStringConvertible {
    var description: String {
        switch self {
        case .salary: return "Salary"
        case .experience: return "Experience"
        case .technology: return "Technology"
        case .unutilized: return "Unutilized"
        case .unutilizedHV: return "Unutilized High Value"
        }
    }
}

Call it

print(Filter.allValues)

Prints:

["Salary", "Experience", "Technology", "Unutilized", "Unutilized High Value"]

Fredericafrederich answered 20/12, 2016 at 12:57 Comment(0)
E
7

I found myself doing .allValues alot throughout my code. I finally figured out a way to simply conform to an Iteratable protocol and have an rawValues() method.

protocol Iteratable {}
extension RawRepresentable where Self: RawRepresentable {

    static func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
        var i = 0
        return AnyIterator {
            let next = withUnsafePointer(to: &i) {
                $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee }
            }
            if next.hashValue != i { return nil }
            i += 1
            return next
        }
    }
}

extension Iteratable where Self: RawRepresentable, Self: Hashable {
    static func hashValues() -> AnyIterator<Self> {
        return iterateEnum(self)
    }

    static func rawValues() -> [Self.RawValue] {
        return hashValues().map({$0.rawValue})
    }
}


// Example
enum Grocery: String, Iteratable {
    case Kroger = "kroger"
    case HEB = "h.e.b."
    case Randalls = "randalls"
}

let groceryHashes = Grocery.hashValues() // AnyIterator<Grocery>
let groceryRawValues = Grocery.rawValues() // ["kroger", "h.e.b.", "randalls"]
Enough answered 1/11, 2016 at 18:48 Comment(0)
G
7

EDIT: Swift Evolution Proposal SE-0194 Derived Collection of Enum Cases proposes a level headed solution to this problem. We see it in Swift 4.2 and newer. The proposal also points out to some workarounds that are similar to some already mentioned here but it might be interesting to see nevertheless.

I will also keep my original post for completeness' sake.


This is yet another approach based on @Peymmankh's answer, adapted to Swift 3.

public protocol EnumCollection: Hashable {}

extension EnumCollection {

public static func allValues() -> [Self] {
    typealias S = Self

    let retVal = AnySequence { () -> AnyIterator<S> in
        var raw = 0
        return AnyIterator {
            let current = withUnsafePointer(to: &raw) {
                 $0.withMemoryRebound(to: S.self, capacity: 1) { $0.pointee }
            }
            guard current.hashValue == raw else { return nil }
            raw += 1
            return current
        }
    }

    return [S](retVal)
}
Gastrocnemius answered 19/1, 2017 at 14:21 Comment(0)
I
5
enum Rank: Int {
    ...
    static let ranks = (Rank.Ace.rawValue ... Rank.King.rawValue).map{Rank(rawValue: $0)! }

}
enum Suit {
    ...
    static let suits = [Spades, Hearts, Diamonds, Clubs]
}

struct Card {
    ...
    static func fullDesk() -> [Card] {
        var desk: [Card] = []
        for suit in Suit.suits {
            for rank in Rank.ranks {
                desk.append(Card(rank: rank,suit: suit))
            }
        }
        return desk
    }
}

How about this?

Intrigante answered 26/6, 2015 at 9:43 Comment(1)
Thanks, it works as I need. But is there any opportunity in map-closure to get the value not by index but by name?Heall
T
5

You can try to enumerate like this

enum Planet: String {
    case Mercury
    case Venus
    case Earth
    case Mars

    static var enumerate: [Planet] {
        var a: [Planet] = []
        switch Planet.Mercury {
            case .Mercury: a.append(.Mercury); fallthrough
            case .Venus: a.append(.Venus); fallthrough
            case .Earth: a.append(.Earth); fallthrough
            case .Mars: a.append(.Mars)
        }
    return a
    }
}

Planet.enumerate // [Mercury, Venus, Earth, Mars]
Tingaling answered 7/12, 2017 at 15:7 Comment(5)
That's a lot of useless code! It's equivalent to static var enumerate = [Mercury, Venus, Earth, Mars], making it a subpar answer compared to most voted answer https://mcmap.net/q/64169/-how-to-enumerate-an-enum-with-string-typeBrecher
@Meridith this answer has the important benefit of using the compiler to guarantee you won't miss a case.Arva
@Meridith that has the same problem of allowing you to make a user error, i.e. the compiler won't complain if you write return [Mercury, Venus, Mars] instead of return [Mercury, Venus, Earth, Mars]Arva
@Arva I decided to post the improvement as an answer, for clarity: https://mcmap.net/q/64169/-how-to-enumerate-an-enum-with-string-typeBrecher
@Meridith If in your new answer you replace the return statement with this return [.spades, .hearts, .clubs] the compiler won't say a thing and then when you try to use it in code, you will get [TestApp.Suit.spades, TestApp.Suit.hearts, TestApp.Suit.clubs] - that was my point - that if you are dealing with a big enum and you have to add or remove cases from time to time, your solution is prone to errors of omission while the current answer, while not concise, is safer.Arva
E
4

This solution strikes the right balance of readability and maintainability.

struct Card {

    // ...

    static func deck() -> Card[] {
        var deck = Card[]()
        for rank in Rank.Ace.toRaw()...Rank.King.toRaw() {
            for suit in [Suit.Spades, .Hearts, .Clubs, .Diamonds] {
                let card = Card(rank: Rank.fromRaw(rank)!, suit: suit)
                deck.append(card)
            }
        }
    return deck
    }
}

let deck = Card.deck()
Endocrine answered 8/6, 2014 at 23:40 Comment(1)
In my opinion, this is the best solution. When I see swift code, mostly, readability is not better than objc. But it could be, if programmers payed greater attention to readers of their code. Their future selves, for example :)Beaston
F
4

Sorry, my answer was specific to how I used this post in what I needed to do. For those who stumble upon this question, looking for a way to find a case within an enum, this is the way to do it (new in Swift 2):

Edit: lowercase camelCase is now the standard for Swift 3 enum values

// From apple docs: If the raw-value type is specified as String and you don’t assign values to the cases explicitly, each unassigned case is implicitly assigned a string with the same text as the name of that case.

enum Theme: String
    {
    case white, blue, green, lavender, grey
    }

func loadTheme(theme: String)
    {
    // this checks the string against the raw value of each enum case (note that the check could result in a nil value, since it's an optional, which is why we introduce the if/let block
    if let testTheme = Theme(rawValue: theme)
        {
        // testTheme is guaranteed to have an enum value at this point
        self.someOtherFunction(testTheme)
        }
    }

For those wondering about the enumerating on an enum, the answers given on this page that include a static var/let containing an array of all enum values are correct. The latest Apple example code for tvOS contains this exact same technique.

That being said, they should build a more convenient mechanism into the language (Apple, are you listening?)!

Filial answered 29/7, 2015 at 18:45 Comment(0)
R
4

In Swift 3, when the underlying enum has rawValue, you could implement the Strideable protocol. The advantages are that no arrays of values are created like in some other suggestions and that the standard Swift "for in" loop works, which makes a nice syntax.

// "Int" to get rawValue, and Strideable so we can iterate
enum MyColorEnum: Int, Strideable {
    case Red
    case Green
    case Blue
    case Black

    // required by Strideable
    typealias Stride = Int

    func advanced(by n:Stride) -> MyColorEnum {
        var next = self.rawValue + n
        if next > MyColorEnum.Black.rawValue {
            next = MyColorEnum.Black.rawValue
        }
        return MyColorEnum(rawValue: next)!
    }

    func distance(to other: MyColorEnum) -> Int {
        return other.rawValue - self.rawValue
    }

    // just for printing
    func simpleDescription() -> String {
        switch self {
        case .Red: return "Red"
        case .Green: return "Green"
        case .Blue: return "Blue"
        case .Black: return "Black"
        }
    }
}

// this is how you use it:
for i in MyColorEnum.Red ... MyColorEnum.Black {
    print("ENUM: \(i)")
}
Riocard answered 20/12, 2016 at 22:49 Comment(1)
Ahh, just what I was looking for to replace ForwardIndexType. Now my iterations look good at the usage site... just the proper Swifty way.Kristoferkristoffer
C
3

The experiment was: EXPERIMENT

Add a method to Card that creates a full deck of cards, with one card of each combination of rank and suit.

So without modifying or enhancing the given code other than adding the method (and without using stuff that hasn't been taught yet), I came up with this solution:

struct Card {
    var rank: Rank
    var suit: Suit

    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }

    func createDeck() -> [Card] {
        var deck: [Card] = []
        for rank in Rank.Ace.rawValue...Rank.King.rawValue {
            for suit in Suit.Spades.rawValue...Suit.Clubs.rawValue {
                let card = Card(rank: Rank(rawValue: rank)!, suit: Suit(rawValue: suit)!)
                //println(card.simpleDescription())
                deck += [card]
            }
        }
        return deck
    }
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
let deck = threeOfSpades.createDeck()
Curable answered 29/11, 2014 at 22:38 Comment(0)
F
3

Here is a method I use to both iterate an enum and provide multiple values types from one enum

enum IterateEnum: Int {
    case Zero
    case One
    case Two
    case Three
    case Four
    case Five
    case Six
    case Seven

    //tuple allows multiple values to be derived from the enum case, and
    //since it is using a switch with no default, if a new case is added,
    //a compiler error will be returned if it doesn't have a value tuple set
    var value: (french: String, spanish: String, japanese: String) {
        switch self {
        case .Zero: return (french: "zéro", spanish: "cero", japanese: "nuru")
        case .One: return (french: "un", spanish: "uno", japanese: "ichi")
        case .Two: return (french: "deux", spanish: "dos", japanese: "ni")
        case .Three: return (french: "trois", spanish: "tres", japanese: "san")
        case .Four: return (french: "quatre", spanish: "cuatro", japanese: "shi")
        case .Five: return (french: "cinq", spanish: "cinco", japanese: "go")
        case .Six: return (french: "six", spanish: "seis", japanese: "roku")
        case .Seven: return (french: "sept", spanish: "siete", japanese: "shichi")
        }
    }

    //Used to iterate enum or otherwise access enum case by index order.
    //Iterate by looping until it returns nil
    static func item(index: Int) -> IterateEnum? {
        return IterateEnum.init(rawValue: index)
    }

    static func numberFromSpanish(number: String) -> IterateEnum? {
        return findItem { $0.value.spanish == number }
    }

    //use block to test value property to retrieve the enum case        
    static func findItem(predicate: ((_: IterateEnum) -> Bool)) -> IterateEnum? {

        var enumIndex: Int = -1
        var enumCase: IterateEnum?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = IterateEnum.item(index: enumIndex)

            if let eCase = enumCase {

                if predicate(eCase) {
                    return eCase
                }
            }
        } while enumCase != nil
        return nil
    }
}

var enumIndex: Int = -1
var enumCase: IterateEnum?

// Iterate until item returns nil
repeat {
    enumIndex += 1
    enumCase = IterateEnum.item(index: enumIndex)
    if let eCase = enumCase {
        print("The number \(eCase) in french: \(eCase.value.french), spanish: \(eCase.value.spanish), japanese: \(eCase.value.japanese)")
    }
} while enumCase != nil

print("Total of \(enumIndex) cases")

let number = IterateEnum.numberFromSpanish(number: "siete")

print("siete in japanese: \((number?.value.japanese ?? "Unknown"))")

This is the output:

The number Zero in french: zéro, spanish: cero, japanese: nuru
The number One in french: un, spanish: uno, japanese: ichi
The number Two in french: deux, spanish: dos, japanese: ni
The number Three in french: trois, spanish: tres, japanese: san
The number Four in french: quatre, spanish: cuatro, japanese: shi
The number Five in french: cinq, spanish: cinco, japanese: go
The number Six in french: six, spanish: seis, japanese: roku
The number Seven in french: sept, spanish: siete, japanese: shichi

Total of 8 cases

siete in japanese: shichi


UPDATE

I recently created a protocol to handle the enumeration. The protocol requires an enum with an Int raw value:

protocol EnumIteration {

    //Used to iterate enum or otherwise access enum case by index order. Iterate by looping until it returns nil

    static func item(index:Int) -> Self?
    static func iterate(item:((index:Int, enumCase:Self)->()), completion:(()->())?) {
    static func findItem(predicate:((enumCase:Self)->Bool)) -> Self?
    static func count() -> Int
}

extension EnumIteration where Self: RawRepresentable, Self.RawValue == Int {

    //Used to iterate enum or otherwise access enum case by index order. Iterate by looping until it returns nil
    static func item(index:Int) -> Self? {
        return Self.init(rawValue: index)
    }

    static func iterate(item:((index:Int, enumCase:Self)->()), completion:(()->())?) {

        var enumIndex:Int = -1
        var enumCase:Self?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = Self.item(enumIndex)

            if let eCase = enumCase {
                item(index: enumIndex, enumCase: eCase)
            }
        } while enumCase != nil
        completion?()
    }

    static func findItem(predicate:((enumCase:Self)->Bool)) -> Self? {

        var enumIndex:Int = -1
        var enumCase:Self?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = Self.item(enumIndex)

            if let eCase = enumCase {

                if predicate(enumCase:eCase) {
                    return eCase
                }
            }
        } while enumCase != nil
        return nil
    }

    static func count() -> Int {
        var enumIndex:Int = -1
        var enumCase:Self?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = Self.item(enumIndex)
        } while enumCase != nil

        //last enumIndex (when enumCase == nil) is equal to the enum count
        return enumIndex
    }
}
Factotum answered 24/2, 2018 at 6:41 Comment(0)
R
2

Enums have toRaw() and fromRaw() methods. So if your raw value is an Int, you can iterate from the first to last enum:

enum Suit: Int {
    case Spades = 1
    case Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
        case .Spades:
            return "spades"
        case .Hearts:
            return "hearts"
        case .Diamonds:
            return "diamonds"
        case .Clubs:
            return "clubs"
        }
    }
}

for i in Suit.Spades.toRaw()...Suit.Clubs.toRaw() {
    if let covertedSuit = Suit.fromRaw(i) {
        let description = covertedSuit.simpleDescription()
    }
}

One gotcha is that you need to test for optional values before running the simpleDescription method, so we set convertedSuit to our value first and then set a constant to convertedSuit.simpleDescription()

Rural answered 3/6, 2014 at 9:1 Comment(1)
The original question was about a String type enum not IntAshlieashlin
A
2

This seems like a hack but if you use raw values you can do something like this

enum Suit: Int {  
    case Spades = 0, Hearts, Diamonds, Clubs  
 ...  
}  

var suitIndex = 0  
while var suit = Suit.fromRaw(suitIndex++) {  
   ...  
}  
Alleviator answered 4/6, 2014 at 0:44 Comment(0)
P
2

Here's my suggested approach. It's not completely satisfactory (I'm very new to Swift and OOP!) but maybe someone can refine it. The idea is to have each enum provide its own range information as .first and .last properties. It adds just two lines of code to each enum: still a bit hard-coded, but at least it's not duplicating the whole set. It does require modifying the Suit enum to be an Int like the Rank enum is, instead of untyped.

Rather than echo the whole solution, here's the code I added to the . enum, somewhere after the case statements (Suit enum is similar):

var first: Int { return Ace.toRaw() }
var last: Int { return King.toRaw() }

and the loop I used to build the deck as an array of String. (The problem definition did not state how the deck was to be structured.)

func createDeck() -> [String] {
    var deck: [String] = []
    var card: String
    for r in Rank.Ace.first...Rank.Ace.last {
        for s in Suit.Hearts.first...Suit.Hearts.last {
            card = Rank.simpleDescription( Rank.fromRaw(r)!)() + " of " + Suit.simpleDescription( Suit.fromRaw(s)!)()
           deck.append( card)
       }
    }
    return deck
}

It's unsatisfactory because the properties are associated to an element rather than to the enum. But it does add clarity to the 'for' loops. I'd like it to say Rank.first instead of Rank.Ace.first. It works (with any element), but it's ugly. Can someone show how to elevate that to the enum level?

And to make it work, I lifted the createDeck method out of the Card struct. I could not figure out how to get a [String] array returned from that struct, and that seems a bad place to put such a method anyway.

Pontias answered 15/8, 2014 at 23:9 Comment(0)
C
2

I did it using computed property, which returns the array of all values (thanks to this post http://natecook.com/blog/2014/10/loopy-random-enum-ideas/). However, it also uses int raw-values, but I don't need to repeat all members of enumeration in separate property.

UPDATE Xcode 6.1 changed a bit a way how to get enum member using rawValue, so I fixed listing. Also fixed small error with wrong first rawValue.

enum ValidSuits: Int {
    case Clubs = 0, Spades, Hearts, Diamonds
    func description() -> String {
        switch self {
        case .Clubs:
            return "♣︎"
        case .Spades:
            return "♠︎"
        case .Diamonds:
            return "♦︎"
        case .Hearts:
            return "♥︎"
        }
    }

    static var allSuits: [ValidSuits] {
        return Array(
            SequenceOf {
                () -> GeneratorOf<ValidSuits> in
                var i=0
                return GeneratorOf<ValidSuits> {
                    return ValidSuits(rawValue: i++)
                }
            }
        )
    }
}
Caoutchouc answered 21/10, 2014 at 4:54 Comment(0)
C
2

While dealing with Swift 2.0 here is my suggestion:

I have added the raw type to Suit enum

enum Suit: Int {

then:

struct Card {
    var rank: Rank
    var suit: Suit


    func fullDeck()-> [Card] {

        var deck = [Card]()

        for i in Rank.Ace.rawValue...Rank.King.rawValue {

            for j in Suit.Spades.rawValue...Suit.Clubs.rawValue {

                deck.append(Card(rank:Rank(rawValue: i)! , suit: Suit(rawValue: j)!))
            }
        }

        return deck
    }
}
Craddock answered 16/7, 2015 at 21:18 Comment(0)
A
2

This is a pretty old post, from Swift 2.0. There are now some better solutions here that use newer features of swift 3.0: Iterating through an Enum in Swift 3.0

And on this question there is a solution that uses a new feature of (the not-yet-released as I write this edit) Swift 4.2: How do I get the count of a Swift enum?


There are lots of good solutions in this thread and others however some of them are very complicated. I like to simplify as much as possible. Here is a solution which may or may not work for different needs but I think it works well in most cases:

enum Number: String {
    case One
    case Two
    case Three
    case Four
    case EndIndex

    func nextCase () -> Number
    {
        switch self {
        case .One:
            return .Two
        case .Two:
            return .Three
        case .Three:
            return .Four
        case .Four:
            return .EndIndex

        /* 
        Add all additional cases above
        */
        case .EndIndex:
            return .EndIndex
        }
    }

    static var allValues: [String] {
        var array: [String] = Array()
        var number = Number.One

        while number != Number.EndIndex {
            array.append(number.rawValue)
            number = number.nextCase()
        }
        return array
    }
}

To iterate:

for item in Number.allValues {
    print("number is: \(item)")
}
Adaurd answered 5/5, 2016 at 21:55 Comment(2)
That feels like a lot of work that's specific to the individual enum you've created - I'm not sure that return [Number.One.rawValue, Number.Two.rawValue, ...] isn't cleaner, in this case.Deliadelian
This is a pretty old post, from Swift 2.0. There are now some better solutions here that use newer features of swift 3.0: #41353094 And on this question there is a solution that uses a new feature of (the not-yet-released as I write this edit) Swift 4.2: #27095378Adaurd
S
2

As with @Kametrixom answer here I believe returning an array would be better than returning AnySequence, since you can have access to all of Array's goodies such as count, etc.

Here's the re-write:

public protocol EnumCollection : Hashable {}
extension EnumCollection {
    public static func allValues() -> [Self] {
        typealias S = Self
        let retVal = AnySequence { () -> AnyGenerator<S> in
            var raw = 0
            return AnyGenerator {
                let current : Self = withUnsafePointer(&raw) { UnsafePointer($0).memory }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }

        return [S](retVal)
    }
}
Snocat answered 10/7, 2016 at 16:27 Comment(0)
D
2

Another solution:

enum Suit: String {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"

    static var count: Int {
        return 4   
    }

    init(index: Int) {
        switch index {
            case 0: self = .spades
            case 1: self = .hearts
            case 2: self = .diamonds
            default: self = .clubs
        }
    }
}

for i in 0..<Suit.count {
    print(Suit(index: i).rawValue)
}
Discotheque answered 12/6, 2017 at 21:59 Comment(0)
C
1

I have used the below method, the assumption is that I know which is the last value in the Rank enum and all the ranks have incremental values after Ace

I prefer this way as it is clean and small, easy to understand

 func cardDeck() -> Card[] {
     var cards: Card[] = []
     let minRank = Rank.Ace.toRaw()
     let maxRank = Rank.King.toRaw()

     for rank in minRank...maxRank {
         if var convertedRank: Rank = Rank.fromRaw(rank) {
             cards.append(Card(rank: convertedRank, suite: Suite.Clubs))
             cards.append(Card(rank: convertedRank, suite: Suite.Diamonds))
             cards.append(Card(rank: convertedRank, suite: Suite.Hearts))
             cards.append(Card(rank: convertedRank, suite: Suite.Spades))
         }
    }

    return cards
}
Cleodel answered 6/6, 2014 at 8:20 Comment(0)
C
1

There is a clever way, and frustrating as it is it illustrates the difference between the two different kinds of enums.

Try this:

    func makeDeck() -> Card[] {
      var deck: Card[] = []
      var suits: Suit[] = [.Hearts, .Diamonds, .Clubs, .Spades]
      for i in 1...13 {
        for suit in suits {
          deck += Card(rank: Rank.fromRaw(i)!, suit: suit)
        }
      }
      return deck
    }

The deal is that an enum backed by numbers (raw values) is implicitly explicitly ordered, whereas an enum that isn't backed by numbers is explicitly implicitly unordered.

E.g. when we give the enum values numbers, the language is cunning enough to figure out what order the numbers are in. If on the other hand we don't give it any ordering, when we try to iterate over the values the language throws its hands up in the air and goes "yes, but which one do you want to go first???"

Other languages which can do this (iterating over unordered enums) might be the same languages where everything is 'under the hood' actually a map or dictionary, and you can iterate over the keys of a map, whether there's any logical ordering or not.

So the trick is to provide it with something that is explicitly ordered, in this case instances of the suits in an array in the order we want. As soon as you give it that, Swift is like "well why didn't you say so in the first place?"

The other shorthand trick is to use the forcing operator on the fromRaw function. This illustrates another 'gotcha' about enums, that the range of possible values to pass in is often larger than the range of enums. For instance if we said Rank.fromRaw(60) there wouldn't be a value returned, so we're using the optional feature of the language, and where we start using optionals, forcing will soon follow. (Or alternately the if let construction which still seems a bit weird to me)

Confrere answered 7/6, 2014 at 13:15 Comment(3)
See also John and Andrew's replies.Confrere
"The deal is that an enum backed by numbers (raw values) is implicitly explicitly ordered, whereas an enum that isn't backed by numbers is explicitly implicitly unordered." -- I disagree. The "natural" order of every enum (regardless of if it's backed by ints) should be the order the enum elements are declared. Usually that would coincide with the order of raw values for integer backed enums anyway, but it should not be required.Hiragana
I'm sure you can think of cases for enums to be backed by ints, but not ordered by the raw value. For example, an enum of positions on a rugby team. The int value could be the jersey number, but you probably wouldn't want to order the positions by their jersey number. For those who aren't familiar with rugby, the jersey number for a given position is (traditionally) constant. For example, the jersey number of the hooker is "2" and the jersey number of the left wing is "11".Hiragana
J
1
enum Rank: Int
{
    case Ace = 0
    case Two, Three, Four, Five, Six, Seve, Eight, Nine, Ten
    case Jack, Queen, King
    case Count
}

enum Suit : Int
{
    case Spades = 0
    case Hearts, Diamonds, Clubs
    case Count
}

struct Card
{
    var rank:Rank
    var suit:Suit
}

class Test
{
    func makeDeck() -> Card[]
    {
        let suitsCount:Int = Suit.Count.toRaw()
        let rankCount:Int = Rank.Count.toRaw()
        let repeatedCard:Card = Card(rank:Rank.Ace, suit:Suit.Spades)
        let deck:Card[] = Card[](count:suitsCount*rankCount, repeatedValue:repeatedCard)

        for i:Int in 0..rankCount
        {
            for j:Int in 0..suitsCount
            {
                deck[i*suitsCount+j] = Card(rank: Rank.fromRaw(i)!, suit: Suit.fromRaw(j)!)
            }
        }
        return deck
    }
}

Based on Rick answer: this is 5 times faster

Jellicoe answered 8/6, 2014 at 19:44 Comment(1)
Adding a Count case will break exhaustive switch implementations and CaseIterable conformance.Brecher
B
1

Sometimes, you may deal with an enumerated type with an underlying raw integer type that changes throughout the software development lifecycle. Here is an example that works well for that case:

public class MyClassThatLoadsTexturesEtc
{
    //...

    // Colors used for gems and sectors.
    public enum Color: Int
    {
        // Colors arranged in order of the spectrum.
        case First = 0
        case Red, Orange, Yellow, Green, Blue, Purple, Pink
        // --> Add more colors here, between the first and last markers.
        case Last
    }

    //...

    public func preloadGems()
    {
        // Preload all gems.
        for i in (Color.First.toRaw() + 1) ..< (Color.Last.toRaw())
        {
            let color = Color.fromRaw(i)!
            loadColoredTextures(forKey: color)
        }
    }

    //...
}
Boise answered 16/9, 2014 at 22:20 Comment(1)
Question is specifically for String type, not Int type.Brecher
M
1

(Improvement on Karthik Kumar answer)

This solution is using the compiler to guarantee you won't miss a case.

enum Suit: String {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"

    static var enumerate: [Suit] {
        switch Suit.spades {
        // make sure the two lines are identical ^_^
        case        .spades, .hearts, .diamonds, .clubs:
            return [.spades, .hearts, .diamonds, .clubs]
        }
    }
}
Meridith answered 18/5, 2018 at 10:41 Comment(0)
F
0

It took me a little more than just one method in the struct like the swift book called for but I set up next functions in the enum. I would have used a protocol I'm not sure why but having rank set as int messes it up.

enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
        case .Ace:
            return "ace"
        case .Jack:
            return "jack"
        case .Queen:
            return "Queen"
        case .King:
            return "King"
        default:
            return String(self.toRaw())
        }
    }
    mutating func next() -> Rank {
        var rank = self
        var rawrank = rank.toRaw()
        var nrank: Rank = self
        rawrank = rawrank + 1
        if let newRank = Rank.fromRaw(rawrank) {
            println("\(newRank.simpleDescription())")
            nrank = newRank
        } else {
            return self
        }
        return nrank
    }
}

enum Suit {
    case Spades, Hearts, Diamonds, Clubs
    func color() -> String {
        switch self {
        case .Spades, .Clubs:
            return "black"
        default:
            return "red"
        }
    }
    func simpleDescription() -> String {
        switch self {
        case .Spades:
            return "spades"
        case .Hearts:
            return "hearts"
        case .Diamonds:
            return "diamonds"
        case .Clubs:
            return "clubs"
        }
    }
    mutating func next() -> Suit {
        switch self {
        case .Spades:
            return Hearts
        case .Hearts:
            return Diamonds
        case .Diamonds:
            return Clubs
        case .Clubs:
            return Spades
        }
    }
}

struct Card {
    var rank: Rank
    var suit: Suit
    func deck() -> Card[] {
        var tRank = self.rank
        var tSuit = self.suit
        let tcards = 52 // we start from 0
        var cards: Card[] = []
        for i in 0..tcards {
            var card = Card(rank: tRank, suit: tSuit)
            cards.append(card)
            tRank = tRank.next()
            tSuit = tSuit.next()
        }
        return cards
    }
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}

var card = Card(rank: .Ace, suit: .Spades)
var deck = card.deck()

I used a little general knowledge but that can be easily rectified by multiplying suits by rank (if you aren't using a standard deck of cards and you'd have to change the enums accordingly and if basically just steps through the different enums. To save time I used ranks rawValues you could do the same for suits if you wanted. However, the example did not have it so I decided to figure it out without changing suits rawValue

Fabio answered 5/6, 2014 at 16:35 Comment(0)
G
0

I added function count(), and iterate the values:

public enum MetricType: Int {
    case mvps = 0
    case allNBA = 1
    case championshipRings = 2
    case finalAppearances = 3
    case gamesPlayed = 4
    case ppg = 5

    static func count() -> Int {
        return (ppg.rawValue) + 1
    }

    static var allValues: [MetricType] {
        var array: [MetricType] = Array()
        var item : MetricType = MetricType.mvps
        while item.rawValue < MetricType.count() {
            array.append(item)
            item = MetricType(rawValue: (item.rawValue + 1))!
        }
    return array
    }
}
Gravestone answered 7/2, 2017 at 19:9 Comment(0)
C
0

I found a somewhat hacky-feeling but much safer way of doing this that doesn't require typing the values twice or referencing the memory of the enum values, making it very unlikely to break.

Basically, instead of using an enum, make a struct with a single instance, and make all of the enum-values constants. The variables can then be queried using a Mirror

public struct Suit{

    // the values
    let spades = "♠"
    let hearts = "♥"
    let diamonds = "♦"
    let clubs = "♣"

    // make a single instance of the Suit struct, Suit.instance
    struct SStruct{static var instance: Suit = Suit()}
    static var instance : Suit{
        get{return SStruct.instance}
        set{SStruct.instance = newValue}
    }

    // an array with all of the raw values
    static var allValues: [String]{
        var values = [String]()

        let mirror = Mirror(reflecting: Suit.instance)
        for (_, v) in mirror.children{
            guard let suit = v as? String else{continue}
            values.append(suit)
        }

        return values
    }
}

If you use this method, to get a single value you'll need to use Suit.instance.clubs or Suit.instance.spades

But all of that is so boring... Let's do some stuff that makes this more like a real enum!

public struct SuitType{

    // store multiple things for each suit
    let spades = Suit("♠", order: 4)
    let hearts = Suit("♥", order: 3)
    let diamonds = Suit("♦", order: 2)
    let clubs = Suit("♣", order: 1)

    struct SStruct{static var instance: SuitType = SuitType()}
    static var instance : SuitType{
        get{return SStruct.instance}
        set{SStruct.instance = newValue}
    }

    // a dictionary mapping the raw values to the values
    static var allValuesDictionary: [String : Suit]{
        var values = [String : Suit]()

        let mirror = Mirror(reflecting: SuitType.instance)
        for (_, v) in mirror.children{
            guard let suit = v as? Suit else{continue}
            values[suit.rawValue] = suit
        }

        return values
    }
}

public struct Suit: RawRepresentable, Hashable{
    public var rawValue: String
    public typealias RawValue = String

    public var hashValue: Int{
        // find some integer that can be used to uniquely identify
        // each value. In this case, we could have used the order
        // variable because it is a unique value, yet to make this
        // apply to more cases, the hash table address of rawValue
        // will be returned, which should work in almost all cases
        // 
        // you could also add a hashValue parameter to init() and
        // give each suit a different hash value
        return rawValue.hash
    }

    public var order: Int
    public init(_ value: String, order: Int){
        self.rawValue = value
        self.order = order
    }

    // an array of all of the Suit values
    static var allValues: [Suit]{
        var values = [Suit]()

        let mirror = Mirror(reflecting: SuitType.instance)
        for (_, v) in mirror.children{
            guard let suit = v as? Suit else{continue}
            values.append(suit)
        }

        return values
    }

    // allows for using Suit(rawValue: "♦"), like a normal enum
    public init?(rawValue: String){
        // get the Suit from allValuesDictionary in SuitType, or return nil if that raw value doesn't exist
        guard let suit = SuitType.allValuesDictionary[rawValue] else{return nil}
        // initialize a new Suit with the same properties as that with the same raw value
        self.init(suit.rawValue, order: suit.order)
    }
}

You can now do stuff like

let allSuits: [Suit] = Suit.allValues

or

for suit in Suit.allValues{
   print("The suit \(suit.rawValue) has the order \(suit.order)")
}

However, To get a single you'll still need to use SuitType.instance.spades or SuitType.instance.hearts. To make this a little more intuitive, you could add some code to Suit that allows you to use Suit.type.* instead of SuitType.instance.*

public struct Suit: RawRepresentable, Hashable{
   // ...your code...

   static var type = SuitType.instance

   // ...more of your code...
}

You can now use Suit.type.diamonds instead of SuitType.instance.diamonds, or Suit.type.clubs instead of SuitType.instance.clubs

Cipher answered 22/10, 2017 at 1:43 Comment(0)
C
-1

My solution is to declare an array with all the enum possibilities. So for loop can traverse through all of them.

//Function inside struct Card
static func generateFullDeck() -> [Card] {
    let allRanks = [Rank.Ace, Rank.Two, Rank.Three, Rank.Four, Rank.Five, Rank.Six, Rank.Seven, Rank.Eight, Rank.Nine, Rank.Ten, Rank.Jack, Rank.Queen, Rank.King]
    let allSuits = [Suit.Hearts, Suit.Diamonds, Suit.Clubs, Suit.Spades]
    var myFullDeck: [Card] = []

    for myRank in allRanks {
        for mySuit in allSuits {
            myFullDeck.append(Card(rank: myRank, suit: mySuit))
        }
    }
    return myFullDeck
}

//actual use:
let aFullDeck = Card.generateFullDeck()    //Generate the desired full deck

var allDesc: [String] = []
for aCard in aFullDeck {
    println(aCard.simpleDescription())    //You'll see all the results in playground
}
Citriculture answered 17/5, 2015 at 6:46 Comment(0)
M
-2

On Swift, enum types can be accessed like EnumType.Case:

let tableView = UITableView(frame: self.view.bounds, style: UITableViewStyle.Plain)

Most of the time you're only going to use enum types when you have a few options to work with, and know exactly what you're going to do on each one.

It wouldn't make much sense to use the for-in structure when working with enum types.

You can do this, for instance:

func sumNumbers(numbers : Int...) -> Int {
    var sum = 0

    for number in numbers{
        sum += number
    }

    return sum
}
Malan answered 3/6, 2014 at 5:52 Comment(13)
I don't think your assertion is correct at all. The whole point of an enumeration is that I want to express a range of values (of any type). Just as in the specification of a for loop I can specify a range of integers, I may want to do the same with more complex types. I think Apple have missed a trick here.Angevin
Enums are not for ranges, are for expressing possible states of something. They're not collections, they don't express "range," thus you can't loop through them, it just doesn't make sense.Malan
They are for a set of values, and of course you may wish to iterate across all members of the set. When I say range of integers that is equivalent to a set of integers.Angevin
Think of .Value1 on the UITableViewCellStyle enum... why would it make sense to loop through that enum? To check what style is your cell? If so, then you've got to think about the design decisions you're making.Malan
That's a very utilitarian use of an enum. Think of the case Apple give in the Swift documentation. The suit of the cards does not represent state, it represents the set of possible suits. As you gain experience with a wider range of languages you'll see other applications of enums that software engineers far more experienced than your or I would be more than happy with in their designs.Angevin
@Angevin - I've programmed in a boatload of languages, and I've never come across the need to actually iterate over all the members of an enum. If you need to iterate over something, you put it in an iterable structure like an array.Overland
That's because the enums are not meant to be iterated upon :-)Malan
@Yawar: how about a UI which lets you select the color of something? The available colors are the enum. How would you add each possible color to the picker without iterating over the enum?Hiragana
@AlvinThompson - like I said, put the colours in an iterable structure like an array.Overland
@Yawar: ...and how would you put the colors in an array if you can't iterate through the enum values? Hard code it? That would be defeating one of the major points of using enums (the values are defined in only one place). Just use an array instead? That would defeat another major point of enums (type safety). To say there's no conceivable reason ever to iterate over enum values is patently ridiculous.Hiragana
You can have an array of enums, and iterate through that array, but not iterate through the actual enums.Malan
@AlvinThompson - since SO isn't a great place to have an extended discussion, I've created a thread on Reddit: reddit.com/r/learnprogramming/comments/283vkr/…. Please feel free to reply there.Overland
@OscarSwanros: and once again, how do you put the enums in the array?Hiragana
H
-3

Here's a less cryptic example if you still wanted to use enums for Rank And Suit. Just collect them into an Array if you want to use a for-in loop to iterate over each one.

Example of a standard 52-card deck:

enum Rank: Int {
    case Ace = 1, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King
    func name() -> String {
        switch self {
        case .Ace:
            return "ace"
        case .Jack:
            return "jack"
        case .Queen:
            return "queen"
        case .King:
            return "king"
        default:
            return String(self.toRaw())
        }
    }
}

enum Suit: Int {
    case Diamonds = 1, Clubs, Hearts, Spades
    func name() -> String {
        switch self {
        case .Diamonds:
            return "diamonds"
        case .Clubs:
            return "clubs"
        case .Hearts:
            return "hearts"
        case .Spades:
            return "spades"
        default:
            return "NOT A VALID SUIT"
        }
    }
}

let Ranks = [
    Rank.Ace,
    Rank.Two,
    Rank.Three,
    Rank.Four,
    Rank.Five,
    Rank.Six,
    Rank.Seven,
    Rank.Eight,
    Rank.Nine,
    Rank.Ten,
    Rank.Jack,
    Rank.Queen,
    Rank.King
]

let Suits = [
    Suit.Diamonds,
    Suit.Clubs,
    Suit.Hearts,
    Suit.Spades
]


class Card {
    var rank: Rank
    var suit: Suit

    init(rank: Rank, suit: Suit) {
        self.rank = rank
        self.suit = suit
    }
}

class Deck {
    var cards = Card[]()

    init() {
        for rank in Ranks {
            for suit in Suits {
                cards.append(Card(rank: rank, suit: suit))
            }
        }
    }
}

var myDeck = Deck()
myDeck.cards.count  // => 52
Haydenhaydn answered 9/6, 2014 at 6:7 Comment(4)
This defeats one of the major purposes of having enums in the first place. That is, the list of defined values should be in one and only one place. If an enum you're using is defined in a separate library, and the new version of that library adds a value, you would have to remember every single place in all of your projects where you used this approach, modify your code, and recompile. And let's hope the developer that left two years ago wrote this down somewhere (he didn't, or if he did no one remembers where). This would be a maintenance nightmare.Hiragana
Sure, it lends itself to look more like a composition pattern in OOP, but this is a sound implementation of using an enum. The enum is simply providing a valid set of 'kinds' of "something". The benefit here is that there is a fixed "enumerable" set of these 'kinds' - able to be used by anything without lugging a full blown Card class with it.Haydenhaydn
Yep, I agree that the implementation is sound. My issue is simply that it introduces another spot in the code which has to be changed if the enum list changes. For smaller projects this is fine. For very large projects or projects with lots of people working on them, you want to do everything possible to keep the "technical debt" to a minimum. My definition of "technical debt" is anything that you know needs to be fixed later or anything that you need to "keep track of". In this case, you would need to keep track of this code to ensure it stays synchronized with the enum.Hiragana
Great discussion we've got here. In most cases, you wouldn't be mutating a constant enum, but rather extending per use. Obviously, we'll always be reverting back to the "it depends" answerHaydenhaydn
E
-3
enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King

    func simpleDescription() -> String {
        switch self {
        case .Ace: return "ace"
        case .Jack: return "jack"
        case .Queen: return "queen"
        case .King: return "king"
        default: return String(self.toRaw())
        }
    }
}

enum Suit: Int {
    case Spades = 1
    case Hearts, Diamonds, Clubs

    func simpleDescription() -> String {
        switch self {
        case .Spades: return "spades"
        case .Hearts: return "hearts"
        case .Diamonds: return "diamonds"
        case .Clubs: return "clubs"
        }
    }

    func color() -> String {
        switch self {
        case .Spades, .Clubs: return "black"
        case .Hearts, .Diamonds: return "red"
        }
    }
}

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }

    static func createPokers() -> Card[] {
        let ranks = Array(Rank.Ace.toRaw()...Rank.King.toRaw())
        let suits = Array(Suit.Spades.toRaw()...Suit.Clubs.toRaw())
        let cards = suits.reduce(Card[]()) { (tempCards, suit) in
            tempCards + ranks.map { rank in
                Card(rank: Rank.fromRaw(rank)!, suit: Suit.fromRaw(suit)!)
            }
        }
        return cards
    }
}
Eastern answered 7/8, 2014 at 17:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.