How to implement a 'next' property to a CaseIterable enum in Swift
You can extend CaseIterable
constraining Self
to Equatable
. Then you just need to find the index after the firstIndex
of your CaseItareble
enumeration and return the element at that position. If the index is equal to the endIndex
of all cases just return the first element.
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]
}
}
Another option is to constrain AllCases
to BidirectionalCollection
. This will allow you to get the last element of you enumeration, check if it is equal to self and return the first element without the need to iterate your whole collection:
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)!)]
}
}
expanding on CaseIterable next and previous properties:
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)] }
}
Playground testing;
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
How to get next case of enum(i.e. write a circulating method) in Swift 4.2
Some problems with your approach are:
- The
Collection
protocol does not define alast
property. - In order to compare the elements with
==
they have to beEquatable
. - Collection indices are not necessarily integers, they must be incremented
withindex(after:)
.
This seems to be a working solution (tested with Xcode 10.0 beta 2):
extension CaseIterable where Self: Equatable {
func next() -> Self {
let all = Self.allCases
let idx = all.firstIndex(of: self)!
let next = all.index(after: idx)
return all[next == all.endIndex ? all.startIndex : next]
}
}
Example:
enum Direction: CaseIterable {
case east, south, west, north
}
print(Direction.east.next()) // south
print(Direction.north.next()) // east
Remarks:
- Only enumerations without associated values are
CaseIterable
, and
those are alsoEquatable
(but the compiler does not figure out that
by itself). ThereforeSelf: Equatable
is not a
real restriction. Self.allCases
can be used in Swift 4.2 to access the type property
from an instance method.- The forced unwrapping is safe because we know that the value is
an element ofallCases
. - Your
enum Direction: CaseIterable
compiles because the concreteenum Direction
type isEquatable
, and itsDirection.allCases
is anArray
– which has integer indices and alast
property.
how to use an array as a case of an enum
Just create a static property:
enum Types: String, Codable, CaseIterable {
case type1, type2, type3, type4, type5
static let someCases: [Types] = [.type1, .type2, .type3]
}
Types.someCases // [type1, type2, type3]
Swift Increment through Enum
Update Starting with Swift 4.2 you can make use of the newly added support CaseIterable
protocol, which adds compiler support for generating a list of all cases for an enum. Though @ninestones's comment pointed put that we are not guaranteed for allCases
to return the cases in the same order as defined, the synthesized implementation does this, and it's unlikely that definition will change.
Your enum could then look something like this (no more hardcoded start value):
enum CopyState: CaseIterable {
case binary, hex, both
mutating func next() {
let allCases = type(of: self).allCases
self = allCases[(allCases.index(of: self)! + 1) % allCases.count]
}
}
You can make this piece of functionality available to all CaseIterable
enums:
extension CaseIterable where Self: Equatable {
mutating func next() {
let allCases = Self.allCases
// just a sanity check, as the possibility of a enum case to not be
// present in `allCases` is quite low
guard let selfIndex = allCases.index(of: self) else { return }
let nextIndex = Self.allCases.index(after: selfIndex)
self = allCases[nextIndex == allCases.endIndex ? allCases.startIndex : nextIndex]
}
}
enum CopyState: CaseIterable {
case binary, hex, both
}
var state = CopyState.hex
state.next()
print(state) // both
state.next()
print(state) // binary
Or, a little bit more verbose, but with a better separation of concerns:
extension Collection {
// adding support for computing indexes in a circular fashion
func circularIndex(after i: Index) -> Index {
let nextIndex = index(after: i)
return nextIndex == endIndex ? startIndex : nextIndex
}
}
extension Collection where Element: Equatable {
// adding support for retrieving the next element in a circular fashion
func circularElement(after element: Element) -> Element? {
return index(of: element).map { self[circularIndex(after: $0)] }
}
}
// Protocol to allow iterating in place (similar to a type conforming to both Sequence and IteratorProtocol)
protocol InPlaceIterable {
mutating func next()
}
extension InPlaceIterable where Self: CaseIterable, Self: Equatable {
// adding default implementation for enums
mutating func next() {
self = type(of: self).allCases.circularElement(after: self)!
}
}
// now the enums need only the protocol conformances, they get the
// functionalities for free
enum CopyState: CaseIterable, InPlaceIterable {
case binary, hex, both
}
You could use Int
as raw value for your enum (note that this is also the default raw value if you don't specify it), and use it like this:
enum CopyState: Int {
case binary, hex, both
mutating func next(){
self = CopyState(rawValue: rawValue + 1) ?? .binary
}
}
var state = CopyState.hex
state.next()
print(state) // both
state.next()
print(state) // binary
This works fine as long as you have the raw values of the enum cases in consecutive order. By default the compiler assigns consecutive raw values.
You'd also need to keep in mind to update the next()
method if the first case changes, otherwise it will no longer correctly work.
An alternative to the above limitation, suggested by @MartinR, is to force unwrap the raw value zero:
mutating func next(){
self = CopyState(rawValue: rawValue + 1) ?? CopyState(rawValue: 0)!
}
The above code won't require updating the method when the first enum case changes, however it has the potential of crashing the app if the starting raw value of the enum changes.
iOS Swift, Enum CaseIterable extension
Not all enumeration types have an associated RawValue
, and if they have then it is not necessarily a String
.
Therefore you need to restrict the extension to enumeration types which are RawRepresentable
, and define the return value as an array of RawValue
:
extension CaseIterable where Self: RawRepresentable {
static var allValues: [RawValue] {
return allCases.map { $0.rawValue }
}
}
Examples:
enum TypeOptions: String, CaseIterable {
case all
case article
case show
case unknown = "?"
}
print(TypeOptions.allValues) // ["all", "article", "show", "?" ]
enum IntOptions: Int, CaseIterable {
case a = 1
case b = 4
}
print(IntOptions.allValues) // [1, 4]
enum Foo: CaseIterable {
case a
case b
}
// This does not compile:
print(Foo.allValues) // error: Type 'Foo' does not conform to protocol 'RawRepresentable'
Extending a Protocol property to provide a default implementation in Swift
A possible solution is to locate the current value in the allCases
collection,
and the return the next element (or wrap around to the first element):
public protocol CycleValue: CaseIterable, Equatable {
var nextValue: Self { get }
}
public extension CycleValue {
var nextValue: Self {
var idx = Self.allCases.index(of: self)!
Self.allCases.formIndex(after: &idx)
return idx == Self.allCases.endIndex ? Self.allCases.first! : Self.allCases[idx]
}
}
(Note that both forced unwraps are safe!)
Example:
public enum AppThemeAttributes: CycleValue {
case classic, darkMode // etc.
}
let a = AppThemeAttributes.classic
print(a) // classic
let b = a.nextValue
print(b) // darkMode
let c = b.nextValue
print(c) // classic
The protocol must conform to Equatable
for this to compile, but that
is not a real restriction: A CaseIterable
protocol cannot have
associated values, so that the compiler can always synthesize theEquatable
conformance.
Swift: Index of an element in enum (CaseIterable)
Couple of changes are necessary:
- The return type needs to be
Self.AllCases.Index?
rather thanInt?
. In practice, these types will be equivalent, as seen below. - You also need to constrain any types to
Equatable
, because you need to be equatable in order to usefirstIndex(of:)
. Again, in practice, anyCaseIterable
will usually be an enum without associated values, meaning it will be equatable automatically. - (Optional change) This function will never return
nil
, because you're finding one case in aCaseIterable
. So you can remove the optionality on the return type (Self.AllCases.Index
) and force unwrap.
Example:
public extension CaseIterable where Self: Equatable {
public func ordinal() -> Self.AllCases.Index {
return Self.allCases.firstIndex(of: self)!
}
}
enum Example: CaseIterable {
case x
case y
}
Example.y.ordinal() // 1
type(of: Example.y.ordinal()) // Int
Personally, I'd add that "Ordinal" usually means something different than what you're doing, and I'd recommend changing the function name to elementIndex()
or something. But that's an aside.
How to enumerate an enum with String type?
Swift 4.2+
Starting with Swift 4.2 (with Xcode 10), just add protocol conformance to CaseIterable
to benefit from allCases
. To add this protocol conformance, you simply need to write somewhere:
extension Suit: CaseIterable {}
If the enum is your own, you may specify the conformance directly in the declaration:
enum Suit: String, CaseIterable { case spades = "♠"; case hearts = "♥"; case diamonds = "♦"; case clubs = "♣" }
Then the following code will print all possible values:
Suit.allCases.forEach {
print($0.rawValue)
}
Compatibility with earlier Swift versions (3.x and 4.x)
If you need to support Swift 3.x or 4.0, you may mimic the Swift 4.2 implementation by adding the following code:
#if !swift(>=4.2)
public protocol CaseIterable {
associatedtype AllCases: Collection where AllCases.Element == Self
static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
static var allCases: [Self] {
return [Self](AnySequence { () -> AnyIterator<Self> in
var raw = 0
var first: Self?
return AnyIterator {
let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
if raw == 0 {
first = current
} else if current == first {
return nil
}
raw += 1
return current
}
})
}
}
#endif
Related Topics
Swift: Reflecting Properties of Subclass of Nsmanagedobject
Why 'Self.Self' Compiles and Run in Swift
Swift Struct with Lazy, Private Property Conforming to Protocol
Command Failed Due to Signal: Segmentation Fault: 11 While Emitting Ir Sil Function
How to 'Addtarget' to Uilabel in Swift
Custom Mkannotation Not Moving When Coordinate Set
Mandatory Init Override in Swift Uinavigationcontroller Subclass
How to Get Part of a String in Swift
Integrate Existing Aws Cognito User Pool into iOS Project with Amplify
Ambiguous Use of Operator '-' in Swift with 'Abs()'
Mfmailcomposeviewcontroller Error [Mc] Filtering Mail Sheet Accounts for Bundle Id
Swift Firestore Check If Documents Exists
Set Uitextfield Placeholder Color Programmatically
Using @Discardableresult for Closures in Swift
Why Can't I Use .Reduce() in a One-Liner Swift Closure with a Variadic, Anonymous Argument
How to Read Id3 Tags/Other Metadata from an Hls Stream in Swift/Avkit