6

I have a simple enum that I would like to iterate over. For this purpose, I've adopted Sequence and IteratorProtocol as shown in the code below. BTW, this can be copy/pasted to a Playground in Xcode 8.

import UIKit

enum Sections: Int {
  case Section0 = 0
  case Section1
  case Section2
}

extension Sections : Sequence {
  func makeIterator() -> SectionsGenerator {
    return SectionsGenerator()
  }

  struct SectionsGenerator: IteratorProtocol {
    var currentSection = 0

    mutating func next() -> Sections? {
      guard let item = Sections(rawValue:currentSection) else {
        return nil
      }
      currentSection += 1
      return item
    }
  }
}

for section in Sections {
  print(section)
}

But the for-in loop generates the error message "Type 'Sections.Type' does not conform to protocol 'Sequence'". The protocol conformance is in my extension; so, what is wrong with this code?

I know there are other ways of doing this but I'd like to understand what's wrong with this approach.

Thanks.

Phantom59
  • 947
  • 1
  • 8
  • 19
  • There are a number of answers that solve your problem here (the question is about String type, but most of the answers will work with any type): http://stackoverflow.com/questions/24007461/how-to-enumerate-an-enum-with-string-type – kevin Dec 27 '16 at 22:30
  • I have an answer you can refer to here https://stackoverflow.com/a/48960126/5372480 – MkSMC Mar 23 '18 at 20:22

6 Answers6

12

Note that Martin’s solution can be refactored as a protocol:

import Foundation

protocol EnumSequence
{
    associatedtype T: RawRepresentable where T.RawValue == Int
    static func all() -> AnySequence<T>
}
extension EnumSequence
{
    static func all() -> AnySequence<T> {
        return AnySequence { return EnumGenerator() }
    }
}

private struct EnumGenerator<T: RawRepresentable>: IteratorProtocol where T.RawValue == Int {
    var index = 0
    mutating func next() -> T? {
        guard let item = T(rawValue: index) else {
            return nil
        }
        index += 1
        return item
    }
}

Then, given an enum

enum Fruits: Int {
    case apple, orange, pear
}

you slap the protocol and a typealias:

enum Fruits: Int, EnumSequence {
    typealias T = Fruits
    case apple, orange, pear
}

Fruits.all().forEach({ print($0) }) // apple orange pear
Jano
  • 62,815
  • 21
  • 164
  • 192
9

Update: As of Swift 4.2, you can simply add protocol conformance to CaseIterable, see How to enumerate an enum with String type?.


You can iterate over a value of a type which conforms to the Sequence protocol. Therefore

for section in Sections.Section0 {
  print(section)
}

would compile and give the expected result. But of course that is not really what you want because the choice of the value is arbitrary and the value itself not needed in the sequence.

As far as I know, there is no way to iterate over a type itself, so that

for section in Sections {
  print(section)
}

compiles. That would require that the "metatype" Sections.Type conforms to Sequence. Perhaps someone proves me wrong.

What you can do is to define a type method which returns a sequence:

extension Sections {
    static func all() -> AnySequence<Sections> {
        return AnySequence {
            return SectionsGenerator()
        }
    }

    struct SectionsGenerator: IteratorProtocol {
        var currentSection = 0

        mutating func next() -> Sections? {
            guard let item = Sections(rawValue:currentSection) else {
                return nil
            }
            currentSection += 1
            return item
        }
    }

}

for section in Sections.all() {
    print(section)
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • There's an SE Proposal for essentially the "metatype" you suggest; see https://gist.github.com/andyyhope/2fc5b6bee8ee1346f688 Hopefully it's adopted as it would appear to be an overall useful feature that would avoid all sorts of custom solutions. For now, I'll stick with iterating over the type. Thanks! – Phantom59 Dec 27 '16 at 23:02
  • Another reference on its status can be found here: https://github.com/apple/swift-evolution/pull/114 – Kdawgwilk Mar 01 '17 at 05:21
  • Please, also check [this](http://stackoverflow.com/a/28341290/4041795) answer.. which shows how to create a iterateEnum() function for many versions of Swift. – Montmons Apr 10 '17 at 16:38
5

Simply add to enum: static var allTypes: [Sections] = [.Section0, .Section1, .Section2]

And than you can:

Sections.allTypes.forEach { (section) in
            print("\(section)")
}
Serge Bilyk
  • 875
  • 9
  • 10
3

This looks so much simpler:

public protocol EnumSequence {
    init?(rawValue: Int)
}

public extension EnumSequence {

    public static var items: [Self] {
        var caseIndex: Int = 0
        let interator: AnyIterator<Self> = AnyIterator {
            let result = Self(rawValue: caseIndex)
            caseIndex += 1
            return result
        }
        return Array(interator)
    }
}
1

Iterated upon the solutions above, see below a protocol that can be implemented by enumerations to add the allValues sequence but also to allow the possibility to convert to and from string value.

Very convenient for String-like enumerations which need to support objective c (only int enumerations are allowed there).

public protocol ObjcEnumeration: LosslessStringConvertible, RawRepresentable where RawValue == Int {
    static var allValues: AnySequence<Self> { get }
}

public extension ObjcEnumeration {
    public static var allValues: AnySequence<Self> {
        return AnySequence {
            return IntegerEnumIterator()
        }
    }

    public init?(_ description: String) {
        guard let enumValue = Self.allValues.first(where: { $0.description == description }) else {
            return nil
        }
        self.init(rawValue: enumValue.rawValue)
    }

    public var description: String {
        return String(describing: self)
    }
}

fileprivate struct IntegerEnumIterator<T: RawRepresentable>: IteratorProtocol where T.RawValue == Int {
    private var index = 0
    mutating func next() -> T? {
        defer {
            index += 1
        }
        return T(rawValue: index)
    }
}

For a concrete example:

@objc
enum Fruit: Int, ObjcEnumeration {
    case apple, orange, pear
}

Now you can do:

for fruit in Fruit.allValues {

    //Prints: "apple", "orange", "pear"
    print("Fruit: \(fruit.description)")

    if let otherFruit = Fruit(fruit.description), fruit == otherFruit {
        print("Fruit could be constructed successfully from its description!")
    }
}
Werner Altewischer
  • 10,080
  • 4
  • 53
  • 60
1

If your enum is an Int based one, you can do an effective but slightly dirty trick like this.

enum MyEnum: Int {
    case One
    case Two
}

extension MyEnum {
    func static allCases() -> [MyEnum] {
        var allCases = [MyEnum]()
        for i in 0..<10000 {
            if let type = MyEnum(rawValue: i) {
                allCases.append(type)
            } else {
                break
            }
        }
        return allCases
    }
}

Then loop over MyEnum.allCases()..

Mike S
  • 4,092
  • 5
  • 35
  • 68