How to create a custom UIPickerView with enums and generics

Illustration of a calendar/date picker

A guide to setting up independent UIPickerViews

A recent project required us to build a set of interdependent UIPickerViews. We wanted a user to be able to set a periodic reminder at specific lengths of time with each selection having a different sub-selection specifying a particular starting point.

  • The periods were weekly, monthly and quarterly
  • The weekly sub-selection was the day of the week (Mon, Tue, Wed, Thu, Fri, Sat, Sun)
  • The monthly sub-selection was the date in the month (1st, 2nd, 3rd …)
  • The quarterly sub-selection was the 1st day of which month in the quarter (first month, second month, third month)

The first picker would contain the options (Weekly, Monthly, Quarterly) and when one option was picked, the second picker would be populated with the corresponding sub-options.

UIPickerView

A custom UIPickerView needs a datasource containing all of the possible selection options and a delegate to handle picker selection, there are many tutorials that work through creating a custom UIPickerView so I won’t go into detail in this tutorial. For now we can define a struct that describes a picker entry

This struct currently holds the string to display in the picker but will allow us to eventually add more useful information about the item being picked.

We can create a helper class that defines the data contained in the picker and controls how the picker reacts. This class will conform to the UIPickerViewDelegate and UIPickerViewDataSource protocols.

Enums

To link the first selection to its sub-selection I decided to use enums with associated values. An associated value is an object of any given type that is stored within an enum case. Each case can store a different value type if needed. The week, month and quarter cases will each hold an associated enum value describing each sub-option. We first create the enum types for the sub-options. Each sub-option enum will contain a title string variable with the text to use in the picker.

We can then use these sub option enums as the associated value of the top level FrequencyOption enum.

Generics

Now that we have our enum, how do we populate the UIPickerViews? We need to pass the list of all possible enum cases into the PickerDataSource in order to do this the PickerRow will have to hold either a FrequencyOption case or a WeekOption, MonthOption or QuarterOption enum cases. But as these are all different types we need to change the PickerRow to use Swift generics.

As described in the Swift language guide[1]

“Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.”

We will make the PickerRow struct a generic that can hold any of the enum types defined above.

As the PickerRow is now generic, we need to make the PickerDataSource generic as well. We will also define the delegate protocol function that will be called when a PickerRow is selected that will return the type of the PickerRow.

The last piece of the puzzle is how we get the list of all available enum cases. We could manually create the array of each type for each of the enums but this would be quite tedious and if the enum cases were changed or added to we would need to remember to update the lists in every location they are used. Instead of this, we make the enums conform to the CaseIterable protocol. This will, in most cases, expose a property allCases containing a collection of all the cases in the enum. One issue with CaseIterable is that it does not automatically synthesize allCases for enums with associated values, this is because there is no way for the compiler to pick which value the associated value should have. We can however add our own implementation of allCases which allows us to specify default associated values.

The enums WeekOption, MonthOption and QuarterOption will just conform to CaseIterable and have allCases synthesized and the FrequencyOption enum will now look like this:

We can now create 2 custom UIPickerViews that can be populated with the FrequencyOption enum and the corresponding sub option enum. This is the code used to create each custom UIPickerView. I have created a xib file with the UIPickerView and Cancel and Done buttons which are referenced in the code. A RemindersPickerView delegate function is used to pass the picked enum back to the parent ViewController:

Now we can create the FrequencyOption UIPickerView in the containing view controller and create the corresponding sub optionUIPickerView based on the FrequencyOption selection. I haven't included all the ViewController setup code, just the relevant RemindersPickerView code.

With this code when the frequencyOptionLabel or subOptionLabel is tapped we create and show a custom picker of the correct type. The RemindersPickerViewDelegate will then receive a callback when the UIPickerView item is picked. We can then use casting to check which type the picked item is and set the correct FrequencyOption selection.

We can now create a custom generic UIPickerView, populate the picker with all cases of an enum and link 2 picker views together with enum associated values. There is still more work to be done to actually use the selected FrequencyOption to generate an actual time period to use to set a reminder. In part 2 I'll go through the ways we can set up local reminder notifications in Swift and how we can convert the selected FrequencyOption to the required reminder date.


Looking for something else?

Search over 400 blog posts from our team

Want to hear more?

Subscribe to our monthly digest of blogs to stay in the loop and come with us on our journey to make things better!