Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get next case of enum(i.e. write a circulating method) in Swift 4.2

Tags:

enums

swift

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:

  1. all's type is Self.AllCases, which is a Collection type. But in the method above, it's [Direction].
  2. There's an error at line 2 says Value of type 'Self.AllCases' has no member 'last' (Even I avoid to use last, the error at line 3 can't be avoided.)
  3. At line 3, the error is 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? :)

like image 866
ribilynn Avatar asked Jun 29 '18 14:06

ribilynn


People also ask

Can enum have methods Swift?

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.

Can we write functions in enum Swift?

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.

Can we extend enum in Swift?

A Swift extension allows you to add functionality to a type, a class, a struct, an enum, or a protocol.

Can enum extend another enum Swift?

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.


1 Answers

Some problems with your approach are:

  • The Collection protocol does not define a last property.
  • In order to compare the elements with == they have to be Equatable.
  • Collection indices are not necessarily integers, they must be incremented with 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:

  • Only enumerations without associated values are 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.
  • The forced unwrapping is safe because we know that the value is an element of allCases.
  • Your 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.
like image 167
Martin R Avatar answered Oct 11 '22 02:10

Martin R