Extending enum to safely loop between cases

Daily Coding Tip 005

Although an enumeration is not an array, it is sometimes necessary to iterate over the cases that it contains. In my enumeration I have the names of three fruits: apple, banana and pear. My enum conforms to the CaseIterable protocol, and this allows me to have access to an array of my cases.

What if I want to change from my current case to the next case?

The problem with choosing the next case in the array is that I don’t know what the index of my current case is.

That’s what my computed property index does. Arrays have access to the method firstIndex(of:), which will give us an index for any value we want. In any other array, there’s a possibility that the array will not be found. I use the nil coalescing operator ?? to give the endIndex if the value isn’t found. The endIndex is actually after the last valid index in an array, so it is equivalent to no valid index being found.

However in this case, the array we are searching is an array of cases of the enum the current case belongs to.

Therefore the endIndex will never be returned, and is only included to show how to resolve situations where the index returned is nil.

enum MyEnum: CaseIterable {
case apple, banana, pear
}
extension CaseIterable where Self: Equatable {
var index: Self.AllCases.Index {
Self.allCases.firstIndex(of: self) ?? Self.allCases.endIndex
}
var nextCase: Self {
let nextIndex = Self.allCases.index(self.index, offsetBy: 1)
guard Self.allCases.indices.contains(nextIndex)
else {
return self
}
return Self.allCases[nextIndex]
}
var nextCaseLooping: Self {
let nextIndex = Self.allCases.index(self.index, offsetBy: 1)
guard Self.allCases.indices.contains(nextIndex)
else {
return Self.allCases.first ?? self
}
return Self.allCases[nextIndex]
}
var previousCase: Self {
let previousIndex = Self.allCases.index(self.index, offsetBy: -1)
guard Self.allCases.indices.contains(previousIndex)
else {
return self
}
return Self.allCases[previousIndex]
}
var previousCaseLooping: Self {
let previousIndex = Self.allCases.index(self.index, offsetBy: -1)
let lastIndex = Self.allCases.index(Self.allCases.endIndex, offsetBy: -1)
guard Self.allCases.indices.contains(previousIndex)
else {
return Self.allCases[lastIndex]
}
return Self.allCases[previousIndex]
}
}

The rest of the extension properties use my index property in various ways.

We have previousCase and nextCase as computed properties, as well as looping varieties of each of these. Without looping, the previousCase property will stop at the first case and continue to return it without moving out of range.

Similarly the nextCase property will reach the last case without trying to go beyond it.

As you may have guessed, the looping varieties will go straight from last to first or first to last, depending on the direction.

You might notice that I use the indices collection to check whether the index is a valid one.

This saves a lot of errors and is a check I recommend using whenever you try to offset the index of an array.


Get more Daily Coding Tips in your inbox!