Cómo obtener todos los valores de enumeración como una matriz

101

Tengo la siguiente enumeración.

enum EstimateItemStatus: Printable {
    case Pending
    case OnHold
    case Done

    var description: String {
        switch self {
        case .Pending: return "Pending"
        case .OnHold: return "On Hold"
        case .Done: return "Done"
        }
    }

    init?(id : Int) {
        switch id {
        case 1:
            self = .Pending
        case 2:
            self = .OnHold
        case 3:
            self = .Done
        default:
            return nil
        }
    }
}

Necesito obtener todos los valores sin procesar como una matriz de cadenas (así ["Pending", "On Hold", "Done"]).

Agregué este método a la enumeración.

func toArray() -> [String] {
    var n = 1
    return Array(
        GeneratorOf<EstimateItemStatus> {
            return EstimateItemStatus(id: n++)!.description
        }
    )
}

Pero recibo el siguiente error.

No se puede encontrar un inicializador para el tipo 'GeneratorOf' que acepte una lista de argumentos del tipo '(() -> _)'

¿Existe una forma más fácil, mejor o más elegante de hacer esto?

Isuru
fuente
2
puede crear una matriz como let array: [EstimateItemStatus] = [.Pending, .Onhold, .Done]
Kristijan Delivuk
1
@KristijanDelivuk Quiero agregar esta funcionalidad a la enumeración en sí. Así que no tengo que ir y agregarlo en todas partes en otros lugares de las bases de código si alguna vez agrego otro valor a la enumeración.
Isuru
Tengo una respuesta que puede consultar aquí stackoverflow.com/a/48960126/5372480
MSimic

Respuestas:

149

Para Swift 4.2 (Xcode 10) y posterior

Hay un CaseIterableprotocolo:

enum EstimateItemStatus: String, CaseIterable {
    case pending = "Pending"
    case onHold = "OnHold"
    case done = "Done"

    init?(id : Int) {
        switch id {
        case 1: self = .pending
        case 2: self = .onHold
        case 3: self = .done
        default: return nil
        }
    }
}

for value in EstimateItemStatus.allCases {
    print(value)
}

Para Swift <4.2

No, no puede consultar los enumvalores que contiene. Vea este artículo . Tienes que definir una matriz que enumere todos los valores que tienes. Consulte también la solución de Frank Valbuena en " Cómo obtener todos los valores de enumeración como una matriz ".

enum EstimateItemStatus: String {
    case Pending = "Pending"
    case OnHold = "OnHold"
    case Done = "Done"

    static let allValues = [Pending, OnHold, Done]

    init?(id : Int) {
        switch id {
        case 1:
            self = .Pending
        case 2:
            self = .OnHold
        case 3:
            self = .Done
        default:
            return nil
        }
    }
}

for value in EstimateItemStatus.allValues {
    print(value)
}
Código diferente
fuente
Vea esta respuesta: stackoverflow.com/a/28341290/8047 incluido el código Swift 3.
Dan Rosenstark
3
Votación ascendente para la parte allValues, pero no estoy seguro de qué sentir acerca de que una enumeración sea de tipo String pero inicializada con Int.
Tyress
El primer enlace está roto pero parece estar en exceptionshub.com/… ahora.
The Tin Man
39

Swift 4.2 presenta un nuevo protocolo llamadoCaseIterable

enum Fruit : CaseIterable {
    case apple , apricot , orange, lemon
}

que cuando cumpla con, puede obtener una matriz de los enumcasos como este

for fruit in Fruit.allCases {
    print("I like eating \(fruit).")
}
Sh_Khan
fuente
27

Agregue el protocolo CaseIterable a la enumeración:

enum EstimateItemStatus: String, CaseIterable {
    case pending = "Pending"
    case onHold = "OnHold"
    case done = "Done"
}

Uso:

let values: [String] = EstimateItemStatus.allCases.map { $0.rawValue }
//["Pending", "OnHold", "Done"]
Maxwell
fuente
17

Hay otra forma en que al menos es segura en tiempo de compilación:

enum MyEnum {
    case case1
    case case2
    case case3
}

extension MyEnum {
    static var allValues: [MyEnum] {
        var allValues: [MyEnum] = []
        switch (MyEnum.case1) {
        case .case1: allValues.append(.case1); fallthrough
        case .case2: allValues.append(.case2); fallthrough
        case .case3: allValues.append(.case3)
        }
        return allValues
    }
}

Tenga en cuenta que esto funciona para cualquier tipo de enumeración (RawRepresentable o no) y también si agrega un nuevo caso, obtendrá un error del compilador que es bueno ya que lo obligará a tenerlo actualizado.

Frank Valbuena
fuente
1
Poco ortodoxo, pero funciona y le advierte si modifica los casos de enumeración. ¡Solución inteligente!
Chuck Krutsinger
12

Encontré en alguna parte este código:

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
            }
        }
    }
}

Utilizar:

enum YourEnum: EnumCollection { //code }

YourEnum.cases()

devolver lista de casos de YourEnum

Daniel Kuta
fuente
Parece una gran solución pero tiene bastantes errores de compilación en Swift 4.
Isuru
1
El "lugar" puede ser: theswiftdev.com/2017/10/12/swift-enum-all-values (¿entre otros?). El bloguero le da crédito a CoreKit .
AmitaiB
5
Esto se rompe en XCode 10 (independientemente de la versión Swift) como el valor hash de una enumeración ya no es incremental sino aleatorio, rompiendo el mecanismo. La nueva forma de hacer esto es actualizar a Swift 4.2 y usar CaseIterable
Yasper
8

Para obtener una lista con fines funcionales, use la expresión EnumName.allCasesque devuelve una matriz, por ejemplo

EnumName.allCases.map{$0.rawValue} 

le dará una lista de cadenas dado que EnumName: String, CaseIterable

Nota: use en allCaseslugar de AllCases().

吖 奇 说 の archywillhe
fuente
8
enum EstimateItemStatus: String, CaseIterable {
  case pending = "Pending"
  case onHold = "OnHold"
  case done = "Done"

  static var statusList: [String] {
    return EstimateItemStatus.allCases.map { $0.rawValue }
  }
}

["Pendiente", "En espera", "Listo"]

Vladimir Pchelyakov
fuente
2

Actualización para Swift 5

La solución más fácil que he encontrado es usar .allCasesuna enumeración que se extiendeCaseIterable

enum EstimateItemStatus: CaseIterable {
    case Pending
    case OnHold
    case Done

    var description: String {
        switch self {
        case .Pending: return "Pending"
        case .OnHold: return "On Hold"
        case .Done: return "Done"
        }
    }

    init?(id : Int) {
        switch id {
        case 1:
            self = .Pending
        case 2:
            self = .OnHold
        case 3:
            self = .Done
        default:
            return nil
        }
    }
}

.allCasesen cualquier CaseIterableenumeración devolverá un Collectionde ese elemento.

var myEnumArray = EstimateItemStatus.allCases

más información sobre CaseIterable

Christopher Larsen
fuente
No es necesario implementar description (). Simplemente equipare cada caso con la cadena, por ejemplo, case OnHold = "On Hold"y ese se convierte en el valor bruto de cada uno.
pnizzle
@pnizzle Lo sé, está ahí porque estaba en la pregunta original.
Christopher Larsen
1

Para Swift 2

// Found http://stackoverflow.com/questions/24007461/how-to-enumerate-an-enum-with-string-type
func iterateEnum<T where T: Hashable, T: RawRepresentable>(_: 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
        }
    }
}

func arrayEnum<T where T: Hashable, T: RawRepresentable>(type: T.Type) -> [T]{
    return Array(iterateEnum(type))
}

Para usarlo:

arrayEnum(MyEnumClass.self)
Jean-Nicolas Defossé
fuente
¿Por qué los elementos de hashValueser 0..n?
NRitH
1

Después de la inspiración de Sequence y horas de probar n errores. Finalmente obtuve este cómodo y hermoso Swift 4 way en Xcode 9.1:

protocol EnumSequenceElement: Strideable {
    var rawValue: Int { get }
    init?(rawValue: Int)
}

extension EnumSequenceElement {
    func distance(to other: Self) -> Int {
        return other.rawValue - rawValue
    }

    func advanced(by n: Int) -> Self {
        return Self(rawValue: n + rawValue) ?? self
    }
}

struct EnumSequence<T: EnumSequenceElement>: Sequence, IteratorProtocol {
    typealias Element = T

    var current: Element? = T.init(rawValue: 0)

    mutating func next() -> Element? {
        defer {
            if let current = current {
                self.current = T.init(rawValue: current.rawValue + 1)
            }
        }
        return current
    }
}

Uso:

enum EstimateItemStatus: Int, EnumSequenceElement, CustomStringConvertible {
    case Pending
    case OnHold
    case Done

    var description: String {
        switch self {
        case .Pending:
            return "Pending"
        case .OnHold:
            return "On Hold"
        case .Done:
            return "Done"
        }
    }
}

for status in EnumSequence<EstimateItemStatus>() {
    print(status)
}
// Or by countable range iteration
for status: EstimateItemStatus in .Pending ... .Done {
    print(status)
}

Salida:

Pending
On Hold
Done
mclam
fuente
1

Puedes usar

enum Status: Int{
    case a
    case b
    case c

}

extension RawRepresentable where Self.RawValue == Int {

    static var values: [Self] {
        var values: [Self] = []
        var index = 1
        while let element = self.init(rawValue: index) {
            values.append(element)
            index += 1
        }
        return values
    }
}


Status.values.forEach { (st) in
    print(st)
}
Carlos Chaguendo
fuente
¡Agradable! Después de actualizar Swift 3.2 a 4.1, esta fue una solución que utilicé. Originalmente teníamos declaraciones AnyItertor <Self>. Tu solución fue mucho más limpia y fácil de leer. ¡Gracias!
Nick N
2
Sin embargo, hay un error de código aquí. Falta el primer elemento del estuche. Cambiar índice var = 1 a índice var = 0
Nick N
0

Si su enumeración es incremental y está asociada con números, puede usar el rango de números que asigna a los valores de enumeración, así:

// Swift 3
enum EstimateItemStatus: Int {
    case pending = 1,
    onHold
    done
}

let estimateItemStatusValues: [EstimateItemStatus?] = (EstimateItemStatus.pending.rawValue...EstimateItemStatus.done.rawValue).map { EstimateItemStatus(rawValue: $0) }

Esto no funciona con enumeraciones asociadas con cadenas o cualquier otra cosa que no sean números, ¡pero funciona muy bien si ese es el caso!

Ben Patch
fuente
0

Extensión en una enumeración para crear allValues.

extension RawRepresentable where Self: CaseIterable {
      static var allValues: [Self.RawValue] {
        return self.allCases.map { $0.rawValue}
      }
    }
Ankit garg
fuente
Si bien este código puede proporcionar una solución al problema de OP, se recomienda encarecidamente que brinde un contexto adicional sobre por qué y / o cómo este código responde a la pregunta. Las respuestas de solo código generalmente se vuelven inútiles a largo plazo porque los futuros espectadores que experimentan problemas similares no pueden comprender el razonamiento detrás de la solución.
E. Zeytinci