首页 > 解决方案 > 如何在 Swift 中为 CaseIterable 枚举实现“下一个”属性

问题描述

我正在尝试将nextvar 添加到枚举中。我可以为特定的枚举这样做,但想对其进行一般扩展,以便我可以通过指定带有协议的枚举来从枚举值中获取“下一个”枚举案例,例如CaseNextIterable

enum MyEnum: CaseIterable { // 'next' here is possible thanks to 'CaseIterable' protocol
    case a, b, c
    // returns the next case, or first if at end of sequence
    // ie. a.next == b, c.next == a
    var next: Self {
        var r: Self!
        for c in Self.allCases + Self.allCases { // not efficient
            if r != nil {
                r = c
                break
            }
            if c == self {
                r = self
            }
        }
        return r
    }
}

标签: swiftenums

解决方案


您可以将CaseIterable约束扩展SelfEquatable. 然后你只需要在firstIndex你的CaseItareble枚举之后找到索引并返回那个位置的元素。如果索引等于endIndex所有情况中的第一个,则返回第一个元素。

extension CaseIterable where Self: Equatable {
    private var allCases: AllCases { Self.allCases }
    var next: Self {
        let index = allCases.index(after: allCases.firstIndex(of: self)!)
        guard index != allCases.endIndex else { return allCases.first! }
        return allCases[index]
    }
}

另一种选择是约束AllCasesBidirectionalCollection. 这将允许您获取枚举的最后一个元素,检查它是否等于 self 并返回第一个元素,而无需迭代整个集合:

extension CaseIterable where Self: Equatable, AllCases: BidirectionalCollection {
    var allCases: AllCases { Self.allCases }
    var next: Self {
        guard allCases.last != self else { return allCases.first! }
        return allCases[allCases.index(after: allCases.firstIndex(of: self)!)]
    }
}

扩展 CaseIterable 下一个和上一个属性:

extension CaseIterable {
    typealias Index = AllCases.Index
    var first: Self { allCases.first! }
    private var allCases: AllCases { Self.allCases }
    private static func index(after i: Index) -> Index { allCases.index(after: i) }
}

extension CaseIterable where AllCases: BidirectionalCollection {
    var last: Self { allCases.last! }
    private static func index(before i: Index) -> Index { allCases.index(before: i) }
}

extension CaseIterable where Self: Equatable {
    var index: Index { Self.firstIndex(of: self) }
    private static func firstIndex(of element: Self) -> Index { allCases.firstIndex(of: element)! }
}

extension CaseIterable where Self: Equatable, AllCases: BidirectionalCollection {
    var previous: Self { first == self ? last : allCases[Self.index(before: index)] }
    var next: Self { last == self ? first : allCases[Self.index(after: index)] }
}

游乐场测试;

enum Enum: CaseIterable {
    case a,b,c
}

let value: Enum = .c

let next = value.next  // a
let next2 = next.next  // b
let next3 = next2.next // c

let previous = value.previous      // b
let previous2 = previous.previous  // a
let previous3 = previous2.previous // c

推荐阅读