Dynamic UIDatePickers in a Table View - Digital Leaves
1441
post-template-default,single,single-post,postid-1441,single-format-standard,qode-social-login-1.0,qode-restaurant-1.0,ajax_fade,page_not_loaded,,select-theme-ver-4.1,wpb-js-composer js-comp-ver-5.2,vc_responsive

Dynamic UIDatePickers in a Table View

Unlike most date pickers found in web applications, iOS UIDatePicker is one of the  bulkiest controls in the standard UIKit library, and among the most difficult ones to integrate in a screen where multiple controls need to be displayed at once. That’s why Apple wisely decided to make them less obnoxious by hiding them until you need them by means of a clever animation.

Usually, Apple iOS applications collecting data or presenting the user with a set of settings, where information needs to be provided by the user, employ a smart animation in which  time or date field are shown as a textfields or labels in a row inside a table view, and then, if you click on that row, an animation will reveal a hidden UIDatePicker below. The illusion works pretty well and the user is intuitively led to choose a date or time associated to that field.

In this post, we are going to learn how to use this technique to show and dismiss a UIDatePicker in a row inside a table when needed.

Our application

Our application will just be a simplification of an application containing the data of the employees of a company. For simplicity’s sake, we will use just a simple UITableView with the data of one person. The user will be able to modify the data from any of the fields. Among the fields, there would be Date fields that, when clicked, will trigger this animated UIDatePicker appearance, and we will take care of dismissing the UIDatePicker when appropriate using a similar animation.

For the model, we will use a very simple model with just one Person class containing several fields of type ModelFieldType (such as name, email…). Two of those fields, “started work date” and “ended work date” will be Date fields, and we want our UIDatePicker to appear in just those two.

The application will have just one screen. Let’s start by creating our project and setting the model, and then the interface elements in IB!

Our Model

Our model will be pretty simple. Just a Person class. Of course, this is a very simplified model just for this example application.

As you may see, we defined two Date properties, startedWorkingDate and endedWorkingDate. Those properties will be the ones that will trigger the UIDatePicker animated row. Notice how we defined a dateFormatter and two methods dateStringFromDate and dateFromString, that will help us code the date in a human-readable format in our view and get the date back.

We will also define an enumeration, ModelFieldType, defining the type of fields a person can have, which will help us defining and managing them in the table view later

And we’ll define some methods in Person to get and set the properties corresponding to these fields:

Again, we are keeping the model simple to be able to focus on the UIDatePicker row management. These functions will allow us to display and modify the fields for a person automatically in our table view.

Our Main Interface Elements

We’ll create a new project using the template “Single View Application”. We will add a label, an image for the person’s avatar image, and a table view for all the information fields that we might want to query and modify.

We will add our outlets for the label, the image view and the table view, and take care of assigning the table view’s delegate and data source to our view controller.

We will also create two UITableViewCell subclasses. One of them will be called TextFieldTableViewCell, and the other one will be called DatePickerTableViewCell. We will create them as subclasses of UITableViewCell, taking care to check the “Also create XIB file” option to design the visual interface in Interface Building.

 

 

Our Text Field Table View Cell

TextFieldTableViewCell will display one field (property) for a person. It will contain a label with the field name and a UITextField with the value. If the field is a text one (like name, email, etc), this textfield will be enabled for input, so you can modify the name directly there. If the field is a date, the textfield will not be enabled for user interaction, and clicking on the row will trigger the UIDatePicker row.

This class is pretty straightforward, but has some important elements:

  1. We have a delegate (called TextFieldTableViewCellDelegate). This delegate will allow our table view to get informed of important events such as if the user starts editing a text field or if the value of the field changed.
  2. We will use the fieldDidBeginEditing(field) method of our delegate to inform that the user started typing in a textfield. We will do that by implementing UITextFieldDelegate in our class and settings ourselves as the delegate for the textfield. In the textFieldDidBeginEditing(textfield) method we will call our own delegate’s function fieldDidBeginEditing, so the table view can take appropriate actions, such as dismissing any UIDatePicker when the user is editing a different text field. This is optional but it’s a nice polishing detail.
  3. We will set an IBAction for the UITextField in our class to send a message to our delegate’s field:changedValueTo:, in order to notify the table view when the field value has changed.
  4. The configureWithField:andValue:editable: method will configure our cell with the given field, and the editable property will allow us to identify if this is a date field (in which case, editable will be false) or it’s a text field (editable=true).

Our Date Picker Table View Cell

The DatePickerTableView contains just a UIDatePicker covering all the content view for the cell. We will set a height constraint for our date picker (around 140)  so when we expand the cell containing it, the AutoLayout will adjust the size to exactly match our needs.

Similarly to what we had in TextFieldTableViewCell, we will have a delegate to notify back when the date in the UIDatePicker changes:

The configureWithField:currentDate: configures the date picker for the given field and current date value.

(Short) Theory Behind The Animation

How does the animation work and how does the table view insert and remove this UIDatePicker row? Well, let’s suppose we have a simple table like the one depicted in the figure above. The table view has just one section (section 0) and 8 rows (0-7). Let’s suppose that row number 3 is the date row that will trigger the date picker.

We’ll keep a datePickerIndexPath variable at our view controller for keeping the position of the date picker row if it’s currently visible (or nil otherwise). When the user clicks on the date row, via the didSelectRow:atIndexPath: method, we will calculate the position of the date picker row. As the date picker was not showing already, this row is just the row below our clicked row (that’s index path <0-4>). We will add the date picker there, set our datePickerIndexPath, and then we need to take into account that the rest of the index paths “below” our newly inserted row will shift accordingly, so the index path for the row at <0-6> will become <0-7>.

If the user clicks on the same row, we will dismiss the date picker row, so the index of the cells below will get back to their normal correspondence with the data fields.

One additional situation to consider is the scenario in which the date picker is already being shown. Imagine the situation in the right, and let’s suppose we have another date row in index path <0-5> (in the normal situation, at the left). When the date picker is shown, the index path for this row is <0-6>. So if the user clicks on <0-6>, we need to follow these steps:

  1. We need to identify that the cell selected actually corresponds to the field at index 5, not 6, because the date picker is showing, adding 1 to the count of rows below it.
  2. We need to dismiss the current date picker, which is at index <0-4>.
  3. We will need to add the new date picker below the right cell, and update our datePickerIndexPath variable accordingly.

To implement the changes, we will use UITableView’s “beginUpdates” and “endUpdates” methods. These methods serve as a frame for performing updates to the table view that will result in the modification (addition, substitution, deletion) of cells in an animated fashion. Unlike reloadData(), that simply reloads all the data or reloadRowsAtSections: or similar methods, these modifications will be performed specifically in the selected rows, and the animation of the new row appearing or disappearing we get for free is just what we are after visually.

In between beginUpdates and endUpdates, we will call insertRows and deleteRows to append or delete rows respectively. One important caveat to consider is that UIKit expects you to actually do substantial modifications of the tableView between those two methods, so if you don’t modify any row (due to some inner logic), the application will crash. Thus, make sure you are going to insert, delete or replace something before calling them.

Displaying The Cells

 

Ok, now we are ready to do the actual implementation. Let’s start by defining the tableView:numberOfRowsInSection: and tableView:cellForRowAt: methods for our table view in our ViewController.

Let’s get through this code step by step:

  1. We define a list of all of our fields, alongside the fields that are “date fields” and will trigger a UIDatePicker row. Your scenario would probably be more complex and involve more elaborate decision making on where to position the date picker fields.
  2. We create a datePickerIndexPath that will be nil if the date picker is not showing, and a calculated variable to check this condition called datePickerVisible.
  3. In viewDidLoad, we will register the two NIBs for the custom UITableViewCell subclasses we defined earlier. We also set some nice values for the estimated and effective row height for our cells.
  4. The tableView:numberOfRowsInSection: method should return the number of fields contained in our fields variable, and add one more in case our date picker cell is showing.
  5. In tableView:cellForRowAt: we will check if the date picker row is showing and the index path we are configuring is the date picker index path. In this case, we dequeue a DatePickerTableViewCell instance, and configure with the proper field (i.e: the field in the row above, indexPath.row – 1. However, if that’s not the case, we need to dequeue a TextFieldTableViewCell cell and configure it with the right field. The method calculateFieldForIndexPath(indexPath) is in charge of getting the right field for a given index path, regardless on whether the date picker row is showing or not and where it’s located.

Let’s have a look at the calculateFieldForIndexPath method:

This code should be self-explanatory. If the date picker is not showing, we just return the field corresponding to the row of the index path, otherwise, we check if we are above, below or exactly at the date picker index path, and adjust the field index accordingly.

Triggering the Date Picker Row

If you run the application right now, you will see all the fields properly defined in the table view. You can edit the textfields. However, clicking on the date fields will not trigger any interaction. Let’s fix that. First, we

There’s a lot of logic in here, isn’t it? Let’s analyse it step by step:

  1. First of all, we shouldn’t show any date picker for a non-date field. So if the field we selected is a non-date one, we just dismiss the current date picker row and exit. The datePickerShouldAppearForRowSelectionAtIndexPath takes care of that.
  2. We begin the updates in the table view. Whatever the final result, from this point on we need to add, delete or replace a row.
  3. If the date picker is visible, we need to, first of all, close ir, so we delete its row. We take note of the current datePickerIndexPath in the variable “oldDatePickerIndexPath“, because we are going to recalculate it. Then, we need to differentiate between two cases:
    1. If the date picker is right below this index path, that means that the current field is the one that triggered the date picker row, so if selected again, we should just proceed to dismiss it. We do this by simple setting the datePickerIndexPath to nil.
    2. Otherwise, we need to add the date picker to a new location, just below the current index path. To properly calculate the new row for the date picker, we need to take into account if the new date picker is going to be located above the current one (which is going to dissapear) or below, to adjust its index path accordingly. Then, we just insert the row at the index path with the “fade” animation.
  4. If the date picker is currently not showing, then all we need to do is set the date picker index path to the row below as (row + 1) and insert the row.
  5. Finally, we call endUpdates on the table view to commit the changes.

The dismissDatePickerRow method just removes the current date picker index path in the context of a beginUpdates/endUpdates frame, thus performing the animation that shrinks the date picker row and removes it from the table view:

This is the datePickerShouldAppearForRowSelectionAtIndexPath method:

Pretty simple, huh? Now, these are two very useful methods: datePickerIsRightAboveMe and datePickerIsRightBelowMe, they just calculate if the index path passed as argument is just above or below the date picker index path:

 

Final Details

All that’s left is properly reacting to changes in both text and date fields, by means of the delegates that we defined in our custom UITableViewCell subclasses.

The fieldDidBeginEditing method will just dismiss the picker row. This is optional, but I feel like it’s appropriate to dismiss the date picker if you are currently editing a different text row.

Conclusions

In this post, we learned how to invoke a date picker cell that will set a date field from a set of data, and will appear and disappear when the date field is selected. This is convenient due to the bulky appearance of the UIDatePicker control. We learned how to manage several date fields and handle the index path for the added date picker row properly.

As always, you can download the full source code for the project from my Github repository.

Do you have any comments? improvements? Suggestions? Let us know in the comments below!

6 Comments
  • harkening

    July 28, 2017 at 10:18 am Reply

    Well, i can see there is not a single line of code in your provided git repo. That’s sad.

    • Ignacio Nieto Carvajal

      July 29, 2017 at 6:34 am Reply

      Hi there Suhail,
      You are right, my fault. I forgot to do the push. It should be there now, so you can stop being sad! ;)
      Thanks for letting me know.

  • Tomas Tomasson

    November 11, 2017 at 7:56 pm Reply

    Any chance of a tutorial (or referral to a tutorial) where this can be done using storyboards? As a newbie i find myself completely lost following this.

    • Ignacio Nieto Carvajal

      November 13, 2017 at 12:06 pm Reply

      Hi there Tomas,
      Unfortunately, this cannot be done with Storyboards. As you can see, this tutorial is included in the “Advanced” section of my start here page. And for a good reason. It’s not easy stuff for beginners. You should probably learn a little more and be more comfortable with views before trying this.

      That said, I would really like to improve this tutorial to make it more accessible for everybody, so I would love to hear your feedback or suggestions to make it more clean and easy to understand. Please, don’t hesitate to share your thoughts here. Thanks!

  • Justin

    November 15, 2017 at 6:24 am Reply

    where does the “person” variable come from. The model is defined “Person”, but in the ‘configure…’ code in tableView you say ‘person.valueForField

    • Ignacio Nieto Carvajal

      November 15, 2017 at 7:25 am Reply

      Hi there Justin. “person” is an instance variable of the UIViewController containing the table view. Basically, what the table view is doing is displaying the fields for that concrete person. Hope it helps!

Post a Comment

Before you continue...

Hi there! I created the Digital Tips List, the newsletter for Swift and iOS developers, to share my knowledge with you.


It features exclusive weekly tutorials on Swift and iOS development, Swift code snippets and the Digital Tip of the week.


Besides, If you join the list you'll receive the eBook: "Your First iOS App", that will teach you how to build your first iOS App in less than an hour with no prior Swift or iOS knowledge.

I hate spam, and I promise I'll keep your email address safe.