iOS 7 in-line UIDatePicker – Part 2

The third cell is the cell that contains the date picker. We will show and hide this cell as needed. All you need to do to configure this cell is drag a UIDatePicker into the cell. We will need an outlet for it so that we can setup the default date. We will also need an action method for when the value of the date picker changes. We’ll call the outlet datePicker and the method pickerDateChanged: Because we want to be able to hide the cell we will keep an outlet for the cell too, called datePickerCell. We need to configure the date picker to show the date, no time and we can setup a minimum and maximum date for it. You can setup all this in Attributes Inspector.

Finally, the last cell to be configured will only contain a UITextField for the place of birth. Add an outlet called placeOfBirthTextField and make the view controller the delegate for this text field. Also setup the placeholder text to be “Place of birth”.

This is how everything should look like when we’re finished:

finished

In the VCAddPersonTableViewController.m we need to remove all the methods related to the table view data source. Because we are using static cells we don’t need the data source. Remove the following methods: numberOfSectionInTableView:, tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath:. We should now be able to run the project and see the view that we’ve created. Of course it won’t do much for now, but we’re going to fix that.

We need to configure the birthdayLabel to have a default value when we first show the view. We will set this value to now. Let’s create a method called setupBirthdayLabel and call it from viewDidLoad. The method will configure the label like this:

- (void)setupBirthdayLabel {

    self.dateFormatter = [[NSDateFormatter alloc] init];
    [self.dateFormatter setDateStyle:NSDateFormatterMediumStyle];
    [self.dateFormatter setTimeStyle:NSDateFormatterNoStyle];

    NSDate *defaultDate = [NSDate date];

    self.birthdayLabel.text = [self.dateFormatter stringFromDate:defaultDate];
    self.birthdayLabel.textColor = [self.tableView tintColor];

    self.selectedBirthday = defaultDate;
}

Notice that we are using a dateFormatter so you will need to declare a property for that.

The date picker cell is visible all the time. We want it to be hidden until we tap on the cell with the birth date. To hide the date picker cell we will make its height 0. We will do this in tableView:heightForRowAtIndexPath: To make things simpler let’s define a constant to hold the row for the date picker cell. We’ll call that kDatePickerIndex. For the row containing the date picker cell we will return a height of 0.

#define kDatePickerIndex 2

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    CGFloat height = self.tableView.rowHeight;

    if (indexPath.row == kDatePickerIndex){

        height =  0.0f;

    }

    return height;
}

If we run our project now the date picker is gone. Let’s implement the method that will show it or hide it as needed. As expected we will need to do this in the tableView:didSelectRowAtIndexPath:. The only time we have an action to perform is when we select the cell with the date of birth label. When the user taps on that cell we need to show the date picker cell. If the user taps again on it then we need to hide it. So we need a way to know which operation to perform. Let’s define a boolean property to keep track of whether the date picker is showing or is hidden. We’ll call it datePickerIsShowing. Our tableView:didSelectRowAtIndexPath: method will look like this:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    if (indexPath.row == 1){

        if (self.datePickerIsShowing){

            [self hideDatePickerCell];

        }else {            

            [self showDatePickerCell];
        }
    }

    [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}

To hide the date picker we are creating a short animation to make it fade away. We are setting the boolean property to NO and we are calling the beginUpdates and endUpdates on the table view so that everything gets refreshed and animates nicely. To show the date picker we are doing the exact opposite by making it fade in.

- (void)showDatePickerCell {

    self.datePickerIsShowing = YES;

    [self.tableView beginUpdates];

    [self.tableView endUpdates];

    self.datePicker.hidden = NO;
    self.datePicker.alpha = 0.0f;

    [UIView animateWithDuration:0.25 animations:^{

        self.datePicker.alpha = 1.0f;

    }];
}

- (void)hideDatePickerCell {

    self.datePickerIsShowing = NO;

    [self.tableView beginUpdates];
    [self.tableView endUpdates];

    [UIView animateWithDuration:0.25
                     animations:^{
                         self.datePicker.alpha = 0.0f;
    }
                     completion:^(BOOL finished){
                         self.datePicker.hidden = YES;
                     }];
}

There’s one more thing we need to do before we can run the project and see the animation. We need to setup the height for the date picker cell so that it’s 0 only when the date picker is hidden and it’s the height of the date picker otherwise. So let’s change the tableView:heightForRowAtIndexPath: method to do just that

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    CGFloat height = self.tableView.rowHeight;

    if (indexPath.row == kDatePickerIndex){

        height = self.datePickerIsShowing ? kDatePickerCellHeight : 0.0f;

    }

    return height;
}

kDatePickerCellHeight is a constant set to the height of the date picker, in our case is 164. With this change we can now see our date picker appearing and disappearing when we tap the date of birth cell. However, if we select a value, the birthdayLabel doesn’t get updated. To fix this we need to implement the pickerDateChanged: method like this:

- (IBAction)pickerDateChanged:(UIDatePicker *)sender {

    self.birthdayLabel.text =  [self.dateFormatter stringFromDate:sender.date];

    self.selectedBirthday = sender.date;
}

Things look better, but we are still missing a few things. First, if we try to enter some information in the first text field and then tap on the cell that will bring out the date picker we notice that things aren’t looking good. The keyboard doesn’t get dismissed and if you’re testing this on a 3.5 inch device then the date picker will be under the keyboard. And we have the same problem with the text field for the place of birth. So we need to make sure that we dismiss the keyboard when the date picker appears and that we dismiss the date picker when the keyboard will appear. To do this first we need to register for keyboard notifications:

- (void)signUpForKeyboardNotifications {

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow) name:UIKeyboardWillShowNotification object:nil];

}

Call this method from viewDidLoad. The method that gets called when the keyboard will show needs to hide the date picker if it’s visible.

- (void)keyboardWillShow {

    if (self.datePickerIsShowing){

        [self hideDatePickerCell];
    }
}

To dismiss the keyboard we need to tell the active UITextField to resign first responder. Because we have two text fields on the screen we will use a property to remember which one is the first responder at this moment. Before we show the date picker we need to tell the active text field to resign first responder:

[self.activeTextField resignFirstResponder];

To keep track of which text field is active we will use the textFieldDidBeginEditing: method.

- (void)textFieldDidBeginEditing:(UITextField *)textField {

    self.activeTextField = textField;   
}

This solved the problem with the keyboard and the date picker showing at the same time. The last thing we want to do is implement the save button we’ve placed on the navigation bar. We have an action method for it but it’s not implemented yet. What we would like to happen is for the person that we’ve just defined to get added to the people list in the main view controller. To do this we will make the main view controller the delegate for our add person view and send it the details for the person when we press save. The protocol for the delegate will look like this:

@protocol VCAddPersonDelegate <NSObject>

- (void)savePersonDetails:(VCPerson *)person;

@end

We’ll define a delegate property in our table view controller like this:

@property  (weak, nonatomic) id<VCAddPersonDelegate> delegate;

Make it weak to avoid reference cycles. The savePressed: method will create a new person and pass it to the delegate.

- (IBAction)savePressed:(UIBarButtonItem *)sender {

    VCPerson *person = [[VCPerson alloc] initWithName:self.nameTextField.text
                                          dateOfBirth:self.selectedBirthday
                                         placeOfBirth:self.placeOfBirthTextField.text];

    [self.delegate savePersonDetails:person];

    [self dismissViewControllerAnimated:YES completion:NULL];    
}

This will only work if the VCPeopleViewController set itself as the delegate for the VCAddPersonTableViewController. It would do that just before the segue happens:

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    if ([[segue identifier] isEqualToString:kSegueIdentifier]){

        VCAddPersonTableViewController *controller = [[[segue destinationViewController] viewControllers] objectAtIndex:0];

        controller.delegate = self;
    }
}

And it should implement the protocol method:

- (void)savePersonDetails:(VCPerson *)person {

    [self.persons addObject:person];

    NSArray *indexPaths = @[[NSIndexPath indexPathForRow:[self.persons count]-1 inSection:0]];

    [self.tableView insertRowsAtIndexPaths:indexPaths
                          withRowAnimation:UITableViewRowAnimationFade];
}

We have fully implemented the controls of our view and we should now be able to add a new person to our list. If you have any problems feel free to check out the Github project that contains both view controllers implemented as described in this tutorial.

7 Replies to “iOS 7 in-line UIDatePicker – Part 2”

  1. Is this step actually necessary?

      self.datePicker.hidden = NO;
      self.datePicker.alpha = 0.0f;

    Since we are already changing the height of the cell to 0, is there a point of actually setting the datePicker to be hiddne?

  2. I notice that you do have a bug in there. If leave the master view with the Picker visible to go the detail view have the picker visible then save to return back now you have multiple pickers visible ..

  3. When segueing away this fixes the crash. It is related to nilling out the datePickerIndexPath.row before the animation starts that solves the hiding the picker view so that you don’t have 2 pickers views when returning with detail picker viewer showing.

    -(void) viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear: animated];
    if ([self datePickerIsShown]) {
    int row = self.datePickerIndexPath.row;
    self.datePickerIndexPath = nil;

    [self.tableView deleteRowsAtIndexPaths : @[[NSIndexPath indexPathForRow: row inSection:0]]
    withRowAnimation : UITableViewRowAnimationFade];
    }
    }

  4. I thought the “Excellent tutorials” comments are somehow fake. But THIS was the tutorial with which I get this done. It works perfectly – even with Xamarin iOS (C#). Thanks for this great tutorial!

Leave a Reply

Your email address will not be published. Required fields are marked *

*