Show Menu

Need an iOS Developer?

Submit your 30 day Job Listing for FREE

uidatepicker example tutorial ios7

One of the new features in iOS 7 is the in-line UIDatePicker. You might have noticed in the Calendar app or in the Reminder app that when you want to select a date for your event, instead of seeing the UIDatePicker come up from the bottom of the screen like the keyboard, now you see it below the cell that contains the date.

This is how it looks like in the Calendar app:

iOS 7 UIDatePicker

Today, in this UIDatePicker tutorial we are going to see how to replicate this behavior. We are going to create a small project that will demonstrate how to display in-line date pickers in both dynamic and static UITableView cells. Our demo Objective-C application will display a list of people represented by their name and their date of birth. When you select one person an in-line date picker will appear and allow you to change the date of birth. This is the implementation that will use the dynamic prototype cell. On the navigation bar there is a + button that allows you to add a new person. A modal screen will appear and it will allow you to enter the name of the person, its date of birth and the place of birth. This is where we will use static cells. The finished app will look like this:

InlineDatePicker
iOS-InlineDatePicker

Project Setup

Let’s get started by going to File->New Project in Xcode and selecting the Master-Detail Application template.

iOS Master Detail Application

Name your project InlineDatePicker. We’re not using Core Data so deselect that check box. Press Next when ready and on the next screen select where you want to store your project and then click Create. When you run the project you will see something like this:

iOS Master Initial View

You can tap the “+” button to create a new entry and then tap on the new row to see a detail view for it:

iOS Detail View

We already have a working project thanks to Xcode’s template and it is quite close to what we want. We have a table view and a detail view, all we need to do is customise them to suit our needs.

Table view setup

The template created the following file structure for us:

iOS Project File Structure

We are going to make VCMasterViewController display our list of people, so let’s start by giving it a more suitable name. Select VCMasterViewController.h file, then in that file right click on the name of the view controller and choose Refactor->Rename… The new name will be VCPeopleViewController.

iOS Rename Code

Next, select the main storyboard and in the storyboard select the People View Controller. Select the table view and let’s create the prototype cells we need. First we need to tell the table view that it will have two kinds of cells, one that display info about the person and another that displays a UIDatePicker so you can change the birth date. In the Table View section of the Attributes Inspector choose 2 for Prototype Cells like this:

Xcode Dynamic Prototypes

Select one table view cell and change it’s name to Right Detail. Change Accessory to none since we won’t be showing the detail view on selection of the row. Set the Identifier for the cell to be personCell. This is how the cell settings should look like:

Xcode Person Cell

Now select the other table view cell and let’s configure it to display a UIDatePicker. First change the style of the cell to Custom. Next, change the identifier of the cell to be datePickerCell. Change Accessory to none.

Xcode Date Picker Cell

Let’s make the cell bigger so that it will fit a UIDatePicker. Drag a UIDatePicker and size the cell so that it has the same height as the UIDatePicker. We’ll use auto layout to make sure that the picker sticks to all the margins. Select the UIDatePicker and then set the constraints so that there is no space to it’s superview:

Xcode Constraints

Great, now that we have the two types of cells we can configure the table view. Go to VCPeopleViewController.m. Let’s define some constants for the identifiers we used for the two cells:


static NSString *kPersonCellID = @"personCell";
static NSString *kDatePickerCellID = @"datePickerCell";

Next, delete the _objects variable and add a property for the persons array:


@interface VCPeopleViewController ()
 
@property (strong, nonatomic) NSMutableArray *persons;
 
@end

We will need a date formatter to display the birth date in the detail label of the person cell, so let’s define a property for this too:


@property (strong, nonatomic) NSDateFormatter *dateFormatter;

We will instantiate this formatter in viewDidLoad, but first let’s remove all code except the call to super from viewDidLoad. In order to keep everything nice and tidy we will create a helper method just for this:


- (void)createDateFormatter {
 
    self.dateFormatter = [[NSDateFormatter alloc] init];
 
    [self.dateFormatter setDateStyle:NSDateFormatterShortStyle];
 
    [self.dateFormatter setTimeStyle:NSDateFormatterNoStyle];
}

We also want to have something to display right from the start so we need to create some fake data for the persons array. First let’s define a Person class. Go to File->New->File and create a new Objective C class:

Xcode Create Objective-C Class

Call it VCPerson and make sure it is a subclass of NSObject:

Xcode Person Class

A person will have a name, a date of birth and a place of birth. The class will also have a convenience constructor that will have the 3 properties as parameters. The interface will look like this:


#import 
 
@interface VCPerson : NSObject
 
@property (copy, nonatomic) NSString *name;
@property (strong, nonatomic) NSDate *dateOfBirth;
@property (copy, nonatomic) NSString *placeOfBirth;
 
- (id)initWithName:(NSString *)name dateOfBirth:(NSDate *)dateOfBirth placeOfBirth:(NSString *)placeOfBirth;
 
@end

If you’re wondering why I used copy for NSString you might want to have a look at: Use copy for NSString properties

The code in the initialiser will be nothing special, just assigning the arguments to the properties of the class.


- (id)initWithName:(NSString *)name dateOfBirth:(NSDate *)dateOfBirth placeOfBirth:(NSString *)placeOfBirth {
 
    if (self = [super init]){
 
        _name = [name copy];
        _dateOfBirth = dateOfBirth;
        _placeOfBirth = [placeOfBirth copy];
 
    }
 
    return self;
}

Now that we have a data model we can create some fake data. We’ll have a method for that and we’ll call this methods from viewDidLoad in the VCPeopleViewController


- (void)createFakeData {
 
    VCPerson *p1 = [[VCPerson alloc] initWithName:@"John Smith"
                                      dateOfBirth:[NSDate dateWithTimeIntervalSince1970:632448000]
                                     placeOfBirth:@"London"];
 
    VCPerson *p2 = [[VCPerson alloc] initWithName:@"Jane Andersen"
                                      dateOfBirth:[NSDate dateWithTimeIntervalSince1970:123456789]
                                     placeOfBirth:@"San Francisco"];
 
    if (!self.persons){
 
        self.persons = [[NSMutableArray alloc] init];
 
    }
 
    [self.persons addObject:p1];
    [self.persons addObject:p2];
}

The persons array will represent the data source of our table view. The table view will have the same number of rows as persons in the array except when we’re displaying the date picker. We will only be displaying one picker at a certain time so that means that when we are displaying the picker we will have an extra cell. When we are displaying the picker we need to know the index of the cell, so we’ll create a property to hold that:


@property (strong, nonatomic) NSIndexPath *datePickerIndexPath;

We will also create a method to check if the date picker is shown or not based on the property we’ve just defined:


- (BOOL)datePickerIsShown {
 
    return self.datePickerIndexPath != nil;
}

And we will use this new method to know if we need to add an extra row for the date picker or not:


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 
    NSInteger numberOfRows = [self.persons count];
 
    if ([self datePickerIsShown]){
 
        numberOfRows++;
 
    }
 
    return numberOfRows;
}

Next we are going to configure the cells of the table view. We need to know if a row needs to display a person cell or a date picker cell so we use the datePickerIndexPath property for that. In order to simplify the tableView:cellForRowAtIndexPath: method we will create two separate methods for creating each kind of cell and call them when necessary. For the person cell all we do is set the name of the person and the date of birth. For the picker cell we set the date that was in the person cell that triggered the showing of the picker. We want access to the picker so we’ll assign it a tag in Interface Builder so we can retrieve it easily by using viewWithTag. The tag is in the View section of Attributes Inspector. Set the value to 1 and then define a constant for it in VCPeopleViewController so that we don’t use magic numbers.


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    UITableViewCell *cell;
 
    if ([self datePickerIsShown] && (self.datePickerIndexPath.row == indexPath.row)){
 
        VCPerson *person = self.persons[indexPath.row -1];
 
        cell = [self createPickerCell:person.dateOfBirth];
 
    }else {
 
        VCPerson *person = self.persons[indexPath.row];
 
        cell = [self createPersonCell:person];
    }
 
    return cell;
}
 
- (UITableViewCell *)createPersonCell:(VCPerson *)person {
 
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kPersonCellID];
 
    cell.textLabel.text = person.name;
 
    cell.detailTextLabel.text = [self.dateFormatter stringFromDate:person.dateOfBirth];
 
    return cell;
 
}
 
- (UITableViewCell *)createPickerCell:(NSDate *)date {
 
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kDatePickerCellID];
 
    UIDatePicker *targetedDatePicker = (UIDatePicker *)[cell viewWithTag:kDatePickerTag];
 
    [targetedDatePicker setDate:date animated:NO];
 
    return cell;
}

If we run the project we will have the list of people displayed as it should, but when we select one person the detail view will be displayed instead of the in-line date picker. To fix this we must first go to the storyboard and remove the segue that displays the detail view when a cell is selected. After removing the segue we need to tell the table view to display the date picker when a cell is selected by implementing the tableView:didSelectRowAtIndexPath: method.

If the selected cell is the cell that triggered the date picker to be shown then we will hide the date picker. If it’s a different cell then we will need to hide the current showing date picker, if any, then display the new one. This is how the tableView:didSelectRowAtIndexPath: method will look like:


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
    [self.tableView beginUpdates];
 
    if ([self datePickerIsShown] && (self.datePickerIndexPath.row - 1 == indexPath.row)){
 
        [self hideExistingPicker];
 
    }else {
 
        NSIndexPath *newPickerIndexPath = [self calculateIndexPathForNewPicker:indexPath];
 
        if ([self datePickerIsShown]){
 
            [self hideExistingPicker];
 
        }
 
        [self showNewPickerAtIndex:newPickerIndexPath];
 
        self.datePickerIndexPath = [NSIndexPath indexPathForRow:newPickerIndexPath.row + 1 inSection:0];
 
    }
 
    [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
 
    [self.tableView endUpdates];
}

We might have more than one operation of insert/delete so we keep all the table updates together by using beginUpdates and endUpdates methods. Hiding a date picker cell is actually a deletion of that cell from the table view:


- (void)hideExistingPicker {
 
    [self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.datePickerIndexPath.row inSection:0]]
                             withRowAnimation:UITableViewRowAnimationFade];
 
    self.datePickerIndexPath = nil;
}

We will need to calculate the index path for the new date picker to be shown. If a date picker is visible and is above the currently selected index then we need to remember that the cell will be deleted from the table and adjust the index accordingly. If it is below then it won’t impact the new date picker.


- (NSIndexPath *)calculateIndexPathForNewPicker:(NSIndexPath *)selectedIndexPath {
 
    NSIndexPath *newIndexPath;
 
    if (([self datePickerIsShown]) && (self.datePickerIndexPath.row < selectedIndexPath.row)){
 
        newIndexPath = [NSIndexPath indexPathForRow:selectedIndexPath.row - 1 inSection:0];
 
    }else {
 
        newIndexPath = [NSIndexPath indexPathForRow:selectedIndexPath.row  inSection:0];
 
    }
 
    return newIndexPath;
}

And finally, showing the date picker at the index we’ve calculating before is just a matter of inserting a date picker cell in the table view:


- (void)showNewPickerAtIndex:(NSIndexPath *)indexPath {    
 
    NSArray *indexPaths = @[[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:0]];
 
    [self.tableView insertRowsAtIndexPaths:indexPaths
                          withRowAnimation:UITableViewRowAnimationFade];
}

Because the date picker cell has a different height than the person cell we will need to implement the tableView:heightForRowAtIndexPath: method and return a proper height for the date picker cell. We’ll define a property to hold the height for the cell and in viewDidLoad we will deque a date picker cell, get its height and assign it to this property.


UITableViewCell *pickerViewCellToCheck = [self.tableView dequeueReusableCellWithIdentifier:kDatePickerCellID];
self.pickerCellRowHeight = pickerViewCellToCheck.frame.size.height;

Now we can return this height for the date picker cell:


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    CGFloat rowHeight = self.tableView.rowHeight;
 
    if ([self datePickerIsShown] && (self.datePickerIndexPath.row == indexPath.row)){
 
        rowHeight = self.pickerCellRowHeight;
 
    }
 
    return rowHeight;
}

Now the date picker cell will have the right height and will be displayed as we would expect it. The date picker won’t actually change the date because we haven’t setup an action method for that, so let’s fix this. Go into the storyboard and select the date picker, then go to the Connection Inspector and create an action method for when the value is changed:

dateChanged

When this method is triggered we want to get the new date and set it for the detail label of the cell above the date picker.


- (IBAction)dateChanged:(UIDatePicker *)sender {
 
    NSIndexPath *parentCellIndexPath = nil;
 
    if ([self datePickerIsShown]){
 
        parentCellIndexPath = [NSIndexPath indexPathForRow:self.datePickerIndexPath.row - 1 inSection:0];
 
    }else {
 
        return;
    }
 
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:parentCellIndexPath];
    VCPerson *person = self.persons[parentCellIndexPath.row];
    person.dateOfBirth = sender.date;
 
    cell.detailTextLabel.text = [self.dateFormatter stringFromDate:sender.date];
}

We have a functional first screen that displays in-line date picker and changes the date accordingly to the user selection. In part two of this tutorial we will build the second screen with static cells and show how to display an in-line date picker in that context.

having issues?

We have a Questions and Answer section where you can ask your iOS Development questions to thousands of iOS Developers.

Ask Question

FREE Download!

Get your FREE Swift 2 Cheat Sheet and quick reference guide PDF download when you sign up to SwiftMonthly


Sharing is caring

If you enjoyed this tutorial, please help us and others by sharing using one of the social media buttons below.


Written by:

Vasilica Costescu is just a girl that loves her iPhone and iOS programming, she graduated from: West University of Timisoara with a degree in Computer Science in 2006. Her blog has awesome articles. Check it out.

Comments

comments