Swift 4.2 introduces a new CaseIterable
protocol that automatically generates an array property of all cases in an enum.
Now I want to implement a default method for Enum inherits from CaseIterable
that can return the next case of a giving case. If this case is the last case, return the first case. Like a circle.
If I write this for specific Enum, it works correctly:
enum Direction: CaseIterable {
case east, south, west, north
func next() -> Direction {
let all = type(of: self).allCases // 1
if self == all.last! {
return all.first!
} else {
let index = all.firstIndex(of: self)!
return all[index + 1]
}
}
}
print(Direction.east.next()) // south
print(Direction.north.next()) // east
But I want to implement this function to many Enum. Copying and pasting code repeatedly are not good (Not to mention this code is totally the same for every Enum).
So I tried this. But something went wrong.
(I suggest you copy following code to playground that you can understand this problem more quickly):
extension CaseIterable {
func next() -> Self {
let all = type(of: self).allCases // 1
if self == all.last { // 2
return all.first!
} else {
let index = all.firstIndex { (ele) -> Bool in
self == ele // 3
}
return all[index + 1]
}
}
}
Three points:
all
's type is Self.AllCases
, which is a Collection
type. But in the method above, it's [Direction]
.Value of type 'Self.AllCases' has no member 'last'
(Even I avoid to use last
, the error at line 3 can't be avoided.)Binary operator '==' cannot be applied to two 'Self' operands
And even I use generic constraints, it's the same.
func next<T: CaseIterable>(element: T) -> T {...}
Any solutions? :)
Swift's Enum can have methods. It can have instance methods and you can use it to return expression value for the UI. Let's look at the code above.
With the same basic code, you can add a function to an enumeration in Swift. This is something you haven't seen in C and perhaps not in other languages either. It's a new way of looking at enumerations.
A Swift extension allows you to add functionality to a type, a class, a struct, an enum, or a protocol.
We can extend the enum in two orthogonal directions: we can add new methods (or computed properties), or we can add new cases. Adding new methods won't break existing code. Adding a new case, however, will break any switch statement that doesn't have a default case.
Some problems with your approach are:
Collection
protocol does not define a last
property.==
they have to be Equatable
.index(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:
CaseIterable
, and
those are also Equatable
(but the compiler does not figure out that
by itself). Therefore Self: Equatable
is not a
real restriction.Self.allCases
can be used in Swift 4.2 to access the type property
from an instance method.allCases
.enum Direction: CaseIterable
compiles because the concrete
enum Direction
type is Equatable
, and its Direction.allCases
is an Array
– which has integer indices and a last
property.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With