Using Swift Enums and Generics as a data source for UIPickerViews

The Latest on Building the Bipsync Notes iOS App

In our latest iOS project we are using several UIPickerViews with static content in our UI. Historically we’d have used a Plist file or even hard coded values to store the data, along with an enum as the data is unlikely to change. This can feel like overkill for such a simple component.

Thankfully the new CaseIterable protocol available in Swift 4.2 (Xcode 10) makes it simple to use enums as a data source for UIPickerViews that don’t require an additional data source.

For example, using the following enum:

We can access the .allCases array on the enum to populate our UIPickerView with the corresponding data, using the UIPickerViewDelegate and UIPickerViewDataSource protocols:

This is great, but because we have around 5 of these UIPickerView / enum types we would end up with several classes that do the same thing if we created a new class for each UIPickerView. That seems a bit excessive.

Enter Generics

Luckily, we can use Generics to create one class that we can use as a data source and populate with each different enum type.

First we create the struct that’s going to hold our enum data. This is a pretty basic struct that only has two properties: first the type, which is our generic property that will be used to hold our enum type; and then the title, which we store as a string.

As the type property is generic we can pass in the enum type when we initialise the struct. The aim is to represent each of our enums with a key / value pair that we can store in an array. We use the enum type as the key and the rawValue as the value. Since Swift enums conform to the Hashable protocol, they can be used as the key in our array.

To do this, first we create an array of our new structs with the type set to our enum. Then we use Swifts map function to convert the enums to an array of GenericRows as below:

Next, we create the class that we will use as our UIPickerViewDelegate and UIPickerViewDataSource. Again we use a generic type that we can set later on when we initialise the class:

Now we can initialise our GenericPickerDataSource class with the SortMenuOptions type, pass in the items array we created earlier and set this class as the dataSource and delegate properties on our UIPickerView.

Using this method means we can easily add new picker views with an enum data source just by changing the generic type and creating a new instance of the GenericPickerDataSource without the overhead of creating multiple new classes and repeating code.

Employing the Delegate Pattern

One of the advantages of using enums in this way means we can pass values easily to a delegate.

First, we create a new protocol that contains a method which we’ll use to pass an enum to the delegate. As we don’t yet know what enum type is yet we’ll need to use the Any type:

Next we add the delegate property and implement the UIPickerView delegate method didSelectRow on our data source class:

Now, once a user selects a row in our picker view we can notify the delegate of the user’s choice. In order to get the enum type on the delegate we can use a conditional cast to the correct enum type. If the cast is successful then we can use the enum to find the correct type, via a switch statement for example:

Using this method of populating UIPickerView data has helped us to decrease the amount of classes in our codebase. It could also be easily transferred to UITableViews.

Source: https://github.com/richlong/swift-generic-datasource
Apple docs: https://developer.apple.com/documentation/swift/caseiterable