如何在 Swift 中实现 CaseIterable 枚举的 'next' 属性



我正在尝试将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
}
}

您可以将约束SelfCaseIterable扩展到Equatable。然后,您只需要在CaseItareble枚举的firstIndex之后找到索引,并返回该位置的元素。如果索引等于所有情况下的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]
}
}

另一个选项是将AllCases约束为BidirectionalCollection。这将允许您获得枚举的最后一个元素,检查它是否等于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

我在Leo Dabus答案的补充中添加了这个,以防人们也需要previous扩展。


编辑:

我添加了nextprevious

主要区别在于预期的行为。

对于out of band

  • 此解决方案返回nul
  • Leo Dabus的解决方案类似于chained list
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
/// Using `next` on an empty enum of on the last element returns `nil`
var next: Self? {
guard let currentIndex = allCases.firstIndex(of: self) else { return nil }
let index = allCases.index(after: currentIndex)
// ensure we don't go past the last element
guard index != allCases.endIndex else { return nil }
return allCases[index]
}
var previous: Self? {
guard let currentIndex = allCases.firstIndex(of: self) else { return nil }
// ensure we don't go before the first element
guard currentIndex != allCases.startIndex else { return nil }
let index = allCases.index(currentIndex, offsetBy: -1)
return allCases[index]
}
}

最新更新