Как получить все значения перечисления в виде массива

104

У меня есть следующее перечисление.

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

Мне нужно получить все необработанные значения в виде массива строк (например, так ["Pending", "On Hold", "Done"]).

Я добавил этот метод в перечисление.

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

Но я получаю следующую ошибку.

Не удается найти инициализатор для типа GeneratorOf, который принимает список аргументов типа (() -> _) '

Есть ли способ сделать это проще, лучше или элегантнее?

Исуру
источник
2
вы можете создать массив, например let array: [EstimateItemStatus] = [.Pending, .Onhold, .Done]
Кристиан Деливук
1
@KristijanDelivuk Я хочу добавить эту функциональность в само перечисление. Так что мне не нужно идти и добавлять его повсюду в других местах кодовых баз, если я когда-нибудь добавлю еще одно значение в перечисление.
Isuru 05
У меня есть ответ, который вы можете найти здесь stackoverflow.com/a/48960126/5372480
MSimic

Ответы:

156

Для Swift 4.2 (Xcode 10) и новее

Есть CaseIterableпротокол:

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

Для Swift <4.2

Нет, вы не можете запросить, enumкакие значения он содержит. См. Эту статью . Вы должны определить массив, в котором перечислены все ваши значения. Также ознакомьтесь с решением Фрэнка Вальбуэны в разделе « Как получить все значения перечисления в виде массива ».

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)
}
Код другой
источник
См. Этот ответ: stackoverflow.com/a/28341290/8047, включая код Swift 3.
Дэн Розенстарк 01
3
Голосование за часть allValues, но не уверен, что думать о том, что перечисление имеет тип String, но инициализируется с помощью Int.
Tyress
Первая ссылка не работает, но, похоже, сейчас находится на сайте exceptionshub.com/ ...
Железный Человек
40

Swift 4.2 представляет новый протокол под названиемCaseIterable

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

что, если вы соответствуете, вы можете получить массив из таких enumслучаев

for fruit in Fruit.allCases {
    print("I like eating \(fruit).")
}
Ш_Хан
источник
28

Добавьте протокол CaseIterable в перечисление:

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

Применение:

let values: [String] = EstimateItemStatus.allCases.map { $0.rawValue }
//["Pending", "OnHold", "Done"]
Максвелл
источник
17

Есть еще один способ, безопасный по крайней мере во время компиляции:

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

Обратите внимание, что это работает для любого типа перечисления (RawRepresentable или нет), а также, если вы добавите новый кейс, вы получите ошибку компилятора, которая хороша, поскольку заставит вас обновить ее.

Фрэнк Вальбуэна
источник
1
Неортодоксально, но он работает и предупреждает вас, если вы измените enum case. Умное решение!
Чак Крутингер
12

Я где-то нашел этот код:

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

Использование:

enum YourEnum: EnumCollection { //code }

YourEnum.cases()

вернуть список обращений из YourEnum

Даниэль Кута
источник
Похоже на отличное решение, но в Swift 4 довольно много ошибок компиляции.
Isuru
1
«Где-то» может быть: theswiftdev.com/2017/10/12/swift-enum-all-values (среди прочего?). Блогер доверяет CoreKit .
AmitaiB
5
Это не работает в XCode 10 (независимо от версии Swift), поскольку hashValue перечисления больше не инкрементное, а случайное, что нарушает механизм. Новый способ сделать это обновление до Swift 4.2 и использовать CaseIterable
Yasper
10
enum EstimateItemStatus: String, CaseIterable {
  case pending = "Pending"
  case onHold = "OnHold"
  case done = "Done"

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

[«В ожидании», «OnHold», «Готово»]

Владимир Пчеляков
источник
8

Чтобы получить список для функциональных целей, используйте выражение, EnumName.allCasesкоторое возвращает массив, например

EnumName.allCases.map{$0.rawValue} 

предоставит вам список строк, учитывая, что EnumName: String, CaseIterable

Примечание: используйте allCasesвместо AllCases().

吖 奇 说 の archywillhe
источник
2

Обновление для Swift 5

Самое простое решение, которое я нашел, - использовать .allCasesперечисление, которое расширяетCaseIterable

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

.allCasesна любом CaseIterableперечислении вернет a Collectionэтого элемента.

var myEnumArray = EstimateItemStatus.allCases

больше информации о CaseIterable

Кристофер Ларсен
источник
Нет необходимости реализовывать description (). Просто приравняйте каждый регистр к строке, например, case OnHold = "On Hold"и это станет исходным значением для каждого.
pnizzle
@pnizzle Я знаю, это потому, что это было в исходном вопросе.
Кристофер Ларсен
1

Для 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))
}

Чтобы использовать это:

arrayEnum(MyEnumClass.self)
Жан-Николя Дефосе
источник
Почему элементы hashValueдолжны быть 0..n?
NRitH 01
1

После вдохновения от Sequence и часов попыток ошибок. Наконец-то я получил этот удобный и красивый способ Swift 4 на 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
    }
}

Применение:

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

Выход:

Pending
On Hold
Done
Маклам
источник
1

Ты можешь использовать

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)
}
Карлос Чагендо
источник
Ницца! После обновления Swift 3.2 до 4.1 я использовал это решение. Изначально у нас были объявления AnyItertor <Self>. Ваше решение было намного чище и легче читалось. Благодарность!
Nick N
2
Однако здесь есть одна ошибка кода. Не хватает первого предмета в кейсе. Измените var index = 1 на var index = 0
Nick N
0

Если ваше перечисление является инкрементным и связано с числами, вы можете использовать диапазон чисел, который вы сопоставляете со значениями перечисления, например:

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

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

Это не совсем работает с перечислениями, связанными со строками или чем-либо, кроме чисел, но отлично работает, если это так!

Бен Патч
источник
0

Расширение перечисления для создания allValues.

extension RawRepresentable where Self: CaseIterable {
      static var allValues: [Self.RawValue] {
        return self.allCases.map { $0.rawValue}
      }
    }
Анкит Гарг
источник
Хотя этот код может предоставить решение проблемы OP, настоятельно рекомендуется предоставить дополнительный контекст относительно того, почему и / или как этот код отвечает на вопрос. Ответы только на код обычно становятся бесполезными в долгосрочной перспективе, потому что будущие зрители, сталкивающиеся с аналогичными проблемами, не могут понять причину решения.
Э. Зейтинчи