I thought I might write a smaller post today. This is something I needed to do in one of my apps, and thought I would share a bit of how it is done. The actual action to take here is pretty easy and short, but I will discuss a few other things I learned while doing it.
In this app, one thing the user has to do is enter a date. I figured the best way was to have a user tap on a UITextField and instead of the keyboard, have it show a UIDatePicker. The textfield would later be used for just displaying the information selected in that UIDatePicker.
Actually replacing the Keyboard with UIDatePicker
Since I said the main thing to do was easy, lets get right into it. Here is the code to replace the keyboard with a UIDatePicker:
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
[self.someTextField setInputView:datePicker];
And we’re done. Like I said, it was really easy.
Setting UIDatePicker Options
So what else is there to this? Well, in the case of my app, this default date picker shows time of day, which is meaningless in my app, and doesn’t show the year, which I should have. By default, this will generate a UIDatePicker that allows you to set the date by day and time (as in one tumbler is “Sun Jun 15”, next is hour, next is minutes, and finally AM/PM). I just need dates, so then we need to set up the datePicker to do that:
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
datePicker.datePickerMode = UIDatePickerModeDate;
[self.someTextField setInputView:datePicker];
For reference, here are all of the options for the UIDatePickerMode enum:
- UIDatePickerModeTime – (Hour, minute, AM/PM)
- UIDatePickerModeDate – (Month, Day, Year)
- UIDatePickerModeDateAndTime – (Default, (Weekday Month Day), HH, MM, AM/PM)
- UIDatePickerModeCountDownTimer – (Hours (up to 24) and minutes)
The default date that it starts scrolling from is the the date the UIDatePicker was created. If you want to set it to a different date, you can just set the UIDatePicker’s date property (or send a message to setDate).
Updating the UITextField when the UIDatePicker value changes
In the case of my app, I also wanted it to update what is shown in that text box as we scroll around. To do that, I have to trigger a method somewhere to handle doing that. In Interface Builder, you can do that via Control dragging from a control to the assistant editor window. Here you have to set it via code, but it’s pretty easy, here is the creation of our UIDatePicker with that added:
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
datePicker.datePickerMode = UIDatePickerModeDate;
[datePicker addTarget:self action:@selector(updateTextField:)
forControlEvents:UIControlEventValueChanged];
[self.someTextField setInputView:datePicker];
Then elsewhere you have to create the updateTextField method:
-(void)updateTextField:(UIDatePicker *)sender
{
self.someTextField.text = [self.dateFormat stringFromDate:sender.date];
}
Hiding the Keyboard afterward
Finally, like any keyboard on your screen, you must have a way to dismiss it. I want it to hide if somebody clicks outside of the keyboard area, so I use this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[self.view endEditing:YES];
}
This overrides the main touchesBegan on the view controller. When it is run, it first sends the same command to its superclass, so that if there is anything the view itself would do otherwise with these touches, it does. I do not know if this is necessary in this case, but I think it is probably for the best. Anyway, the main part is sending the message to the view, saying to end the editing, and as such hide the keyboard.
A note on performance
You may notice that I did not say WHERE to put the commands above to create your UIDatePicker. Here is the reason why. I first wrote the program, loading that in viewDidLoad. I was testing on an iPhone 4, and found that setting the mode (assigning to datePicker.datePickerMode), took about 150ms. Considering the rest of my startup of loading system resources was about 600 ms, and every other command was in the 10s of milliseconds, I felt I had to do something about it.
I had two options: Put it on a background queue in viewDidLoad, or just generate it when I need it. Since the iPhone 4 has only 1 core, the first may not help as much (I THINK it should help somewhat, but I have not done a LOT of work in multithreading yet), but even if it did, it would have that UIDatePicker just waiting in memory, when it might not even be needed.
For my app, you should set this part when you first get the app, but probably won’t need to set it again for quite some time. If somebody opens my app to check something and doesn’t need to set this date, lets say twice a day for 9 months, that’s about 548 loads, and at 150 ms each, that’s 82.2 seconds. Yes, that is about a minute and a half spread over 9 months, but it still doesn’t need to be there if they don’t change the date often. Premature optimization? Perhaps, but it was so little to fix, I thought I might as well, especially since it increases startup time by 25% or so unnecessarily.
So for me, I decided to run this when somebody taps down on the text box. If they drag their finger off, it did load this unnecessarily, but I would rather load it when the user looks like they might need it, as opposed to just when the app is opened and may not need it. So I attached an action in interface builder to the text box touch down event, and added this:
- (IBAction)someTextFieldTouchDown:(UITextField *)sender
{
if (self.someTextField.inputView == nil)
{
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
datePicker.datePickerMode = UIDatePickerModeDate;
[datePicker addTarget:self action:@selector(updateTextField:)
forControlEvents:UIControlEventValueChanged];
[self.someTextField setInputView:datePicker];
}
}
So that if there is no inputView set (the default is nil, which means to show the normal keyboard), then create this and add it. If not, don’t bother and do nothing. This way, if they tap down, it starts loading it, so by the time they lift their finger it is done, and they see the UIDatePicker. There might be a small delay, but it is not very noticeable, and it is not at startup.
Conclusion
I should really learn from podcasts and not say “this will be a short one”, oh well, at least it was informative, right? I did say that I “might” write a smaller post, not that I necessarily “would”. Anyway, that is how I replaced the keyboard with a UIDatePicker, and some of the things I learned along the way. Also, if you want to change how the date is written in the text box (instead of the default), you might want to read my post Displaying a human readable NSDate.
I hope you found this article helpful. If you did, don’t hesitate to share this post on twitter or your social media of choice. The blog is still pretty new, and every share helps. Of course, if you have any questions, don’t hesitate to contact me on twitter @CodingExplorer, and I’ll see what I can do. Thanks!