18 August 2018 2min read

Swift 4.2 CaseIterable enums and UISegmentedControl, a practical example

On 2014 I wrote a post about how to drive a UISegmentedControl with the new powerful Swift enums. Back then the major complain was that to get all the cases of the enum you had manually list the cases of the enum, which was tiresome and error prone.

This post is a new version of the same idea but updated to Swift 4.2 and the new CaseIterable protocol.

This new protocol has been introduced by the proposal SE-0194. It defines a protocol with a single requirement, a allCases static variable that returns a collection with all possible values of the type.

But the best thing is that the compiler automatically synthesises the conformance for some simple enums.

Drive a UISegmentedControl

Let’s first take a look at how the implementation looked back in 2014.

enum Size: Int, CustomStringConvertible {
    case S = 0
    case M
    case L
    case XL

    static func allValues() -> [String] {
        return [S, M, L, XL].map({$0.description})
    }

    static func count() -> Int {
        return allValues().count
    }

    public var description: String {
        switch self {
        case .S:
            return "S"
        case .M:
            return "M"
        case .L:
            return "L"
        case .XL:
            return "XL"
        }
    }
}

Size.count()
Size.allValues()

let segmented = UISegmentedControl(items: Size.allValues())

We can see how we need to implement a custom function and need to conform to CustomStringConvertible.

Note that the CustomStringConvertible it’s a tradeoff. We had to pick between implementing a custom rawValue or custom string.

Now with Swift 4.2 we can ged rid of both custom implementations leaving our modernised enum like this:

enum Size: Int, CaseIterable {
    case S = 0
    case M
    case L
    case XL
}

With just this conformance we get access to an automatically generated allCases variable.

The CustomStringConvertible can also be removed because Swift now provides better runtime information that allows String interpolation to access the name of the case.

With these improvements we have way less code to maintain and we can still drive our UISegmentedControl as before.

UISegmentedControl(items: Size.allCases.map({ "\($0)" }))
...
let index = sender.selectedSegmentIndex
Size(rawValue: index)

As usual you can find the code for this post in a Playground.

If you want to know more about CaseIterable checkout my video on the topic: Swift 4.2 - CaseIterable - Following Swift Evolution

If you enjoyed this post

Continue reading