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