Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Index of an element in enum (CaseIterable)

Tags:

enums

swift

The following code (compiles without errors) retrieves index of an element in a particular CaseIterable enum type

public enum MyEnum : CaseIterable {
    case ONE, TWO, THREE

    public func ordinal() -> Int? {
       return MyEnum.allCases.firstIndex(of: self)
    }
}

I want to make a generic function to work with all CaseIterable enums.

If I try:

 public extension CaseIterable {

    public func ordinal() -> Int? {
        return CaseIterable.allCases.firstIndex(of: self)
    }
 }

I get a compiler error "Member 'allCases' cannot be used on value of protocol type 'CaseIterable'; use a generic constraint instead" which is quite logical, as the actual enum type is unknown".

When I try CaseIterable<T>, I get another error, as CaseIterable is not declared as generic type.

Is there a way?

like image 236
cyanide Avatar asked Feb 28 '19 19:02

cyanide


People also ask

How to make enum CaseIterable Swift?

To enable it, all you need to do is make your enum conform to the CaseIterable protocol and at compile time Swift will automatically generate an allCases property that is an array of all your enum's cases, in the order you defined them.

What is CaseIterable in Swift?

A type that provides a collection of all of its values.

What is associated enum?

An enum cannot have both raw values and associated values at the same time. The raw values of an enum must be of the same data type. But associated values can be of any type.


1 Answers

Couple of changes are necessary:

  1. The return type needs to be Self.AllCases.Index? rather than Int?. In practice, these types will be equivalent, as seen below.
  2. You also need to constrain any types to Equatable, because you need to be equatable in order to use firstIndex(of:). Again, in practice, any CaseIterable will usually be an enum without associated values, meaning it will be equatable automatically.
  3. (Optional change) This function will never return nil, because you're finding one case in a CaseIterable. 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.

like image 172
Connor Neville Avatar answered Oct 04 '22 05:10

Connor Neville