Como obter todos os valores enum como uma matriz


99

Eu tenho o seguinte enum.

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

Preciso obter todos os valores brutos como uma matriz de strings (assim ["Pending", "On Hold", "Done"]).

Eu adicionei esse método ao enum.

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

Mas estou recebendo o seguinte erro.

Não é possível encontrar um inicializador para o tipo 'GeneratorOf' que aceita uma lista de argumentos do tipo '(() -> _)'

Existe uma maneira mais fácil, melhor ou mais elegante de fazer isso?


2
você pode criar uma matriz como let array: [EstimateItemStatus] = [.Pending, .Onhold, .Done]
Kristijan Delivuk

1
@KristijanDelivuk Desejo adicionar essa funcionalidade ao próprio enum. Portanto, não preciso ir e adicioná-lo em todos os outros lugares das bases de código se algum dia adicionar outro valor ao enum.
Isuru


Eu tenho uma resposta que você pode consultar aqui stackoverflow.com/a/48960126/5372480
MSimic

Respostas:


146

Para Swift 4.2 (Xcode 10) e posterior

Existe um 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

Não, você não pode consultar um enumpara saber quais valores ele contém. Veja este artigo . Você deve definir uma matriz que liste todos os valores que você possui. Verifique também a solução de Frank Valbuena em " Como obter todos os valores enum como uma 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)
}

Veja esta resposta: stackoverflow.com/a/28341290/8047 incluindo o código Swift 3.
Dan Rosenstark

3
Votação positiva para a parte allValues, mas não tenho certeza do que sentir sobre um enum ser do tipo String, mas inicializado com Int.
Tyress

O primeiro link está quebrado, mas parece estar em exceptionshub.com/… agora.
o homem de lata

39

Swift 4.2 apresenta um novo protocolo denominadoCaseIterable

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

que quando você está em conformidade, você pode obter uma matriz de enumcasos como este

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

26

Adicione o protocolo CaseIterable ao enum:

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

Há outra maneira que pelo menos é segura no momento da compilação:

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

Observe que isso funciona para qualquer tipo de enum (RawRepresentable ou não) e também se você adicionar um novo caso, receberá um erro do compilador, o que é bom, pois o forçará a atualizá-lo.


1
Não ortodoxo, mas funciona e avisa se você modificar os casos enum. Solução inteligente!
Chuck Krutsinger de

12

Encontrei em algum lugar 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
            }
        }
    }
}

Usar:

enum YourEnum: EnumCollection { //code }

YourEnum.cases()

retornar lista de casos de YourEnum


Parece ser uma ótima solução, mas tem alguns erros de compilação no Swift 4.
Isuru

1
O "algum lugar" pode ser: theswiftdev.com/2017/10/12/swift-enum-all-values (entre outros?). O blogueiro dá crédito ao CoreKit .
AmitaiB de

5
Isso quebra no XCode 10 (independentemente da versão do Swift) como o hashValue de um enum não mais incremental, mas aleatório, quebrando o mecanismo. A nova maneira de fazer isso é atualizar para o Swift 4.2 e usar CaseIterable
Yasper

8

Para obter uma lista para fins funcionais, use a expressão EnumName.allCasesque retorna um array, por exemplo

EnumName.allCases.map{$0.rawValue} 

lhe dará uma lista de Strings, dado que EnumName: String, CaseIterable

Nota: use em allCasesvez 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 }
  }
}

["Pendente", "OnHold", "Concluído"]


2

Atualização para Swift 5

A solução mais fácil que encontrei é usar .allCasesem um enum que estendeCaseIterable

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

.allCasesem qualquer CaseIterableenum retornará um Collectiondesse elemento.

var myEnumArray = EstimateItemStatus.allCases

mais informações sobre CaseIterable


Não há necessidade de implementar description (). Basta igualar cada caso à string, por exemplo, case OnHold = "On Hold"e isso se torna o valor bruto para cada um.
pnizzle de

@pnizzle eu sei, está lá porque estava na pergunta original.
Christopher Larsen de

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 usá-lo:

arrayEnum(MyEnumClass.self)

Por que os elementos hashValueseriam 0..n?
NRitH 01 de

1

Após inspiração de Sequence e horas de erros try n. Eu finalmente consegui este modo confortável e bonito do Swift 4 no 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)
}

Resultado:

Pending
On Hold
Done

1

Você pode 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)
}

Agradável! Depois de atualizar do Swift 3.2 para o 4.1, esta foi uma solução que usei. Originalmente, tínhamos declarações AnyItertor <Self>. Sua solução ficou muito mais limpa e fácil de ler. obrigado!
Nick N

2
Uma falha de código aqui. Perde o primeiro item da caixa. Altere o índice var = 1 para o índice var = 0
Nick N

0

Se seu enum for incremental e associado a números, você pode usar o intervalo de números que mapeia para valores de enum, assim:

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

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

Isso não funciona muito bem com enums associados a strings ou qualquer coisa diferente de números, mas funciona muito bem se for o caso!


0

Extensão em um enum para criar allValues.

extension RawRepresentable where Self: CaseIterable {
      static var allValues: [Self.RawValue] {
        return self.allCases.map { $0.rawValue}
      }
    }

Embora este código possa fornecer uma solução para o problema do OP, é altamente recomendável que você forneça um contexto adicional sobre por que e / ou como este código responde à pergunta. Respostas apenas em código normalmente se tornam inúteis no longo prazo, porque futuros visualizadores com problemas semelhantes não podem entender o raciocínio por trás da solução.
E. Zeytinci
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.