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?

Isuru
fonte
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
Possível duplicata de Como enumerar um enum com tipo String?
mmmmmm
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)
}
Código Diferente
fonte
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).")
}
Sh_Khan
fonte
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"]
Maxwell
fonte
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.

Frank valbuena
fonte
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

Daniel Kuta
fonte
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().

吖 奇 说 - 何魏奇 Archy Will He
fonte
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"]

Vladimir Pchelyakov
fonte
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

Christopher Larsen
fonte
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)
Jean-Nicolas Defossé
fonte
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
mclam
fonte
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)
}
Carlos Chaguendo
fonte
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!

Ben Patch
fonte
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}
      }
    }
Ankit garg
fonte
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