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?


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

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).")
}

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"]

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.


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


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().


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"]


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


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)

¿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

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

¡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!


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

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.