XCode: Displaying a UIDatePicker when user clicks on UITextbox

34,166

Solution 1

Showing a UIDatePicker for the inputView of a UITextField is relatively hard compared to showing a keyboard, but relatively easy compared to the trouble you've had.

One of the first problems I noticed with your code, and it's a very common problem with those new to iOS/Mac development, is that you are attempting to set the property of an object that doesn't exist yet. Putting "self.dueDate.inputView = datePicker;" in your initWithNibName:bundle: will not work because the view has not yet loaded, and dueDate is part of your view. In objective-c an object is instantiated with alloc & init. Init is the first real method call to any object. At this point in your view controller's life the view has not been created yet. The method call where self.dueDate.inputView = datePicker; belongs is in the viewDidLoad method. It is called exactly when it sounds like it's called, and your dueDate property will be properly loaded at that point. There is no need to use a custom subclass of UITextField to display a date picking view for for your text field. Here is a very basic example of a custom input view class:

ExampleBasicDateInputView.h:

#import <UIKit/UIKit.h>

@interface ExampleBasicDateInputView : UIView
@property (strong,nonatomic) UIDatePicker *datePicker;
@end

ExampleBasicDateInputView.m:

#import "ExampleBasicDateInputView.h"

@implementation ExampleBasicDateInputView{
    UITextField *_inputField; // ivar to store the textfield currently being edited
}
@synthesize datePicker = _datePicker;
// TARGET METHODS
-(void)pickerValueChanged:(UIDatePicker *)picker{
    _inputField.text = self.datePicker.date.description; // set text to date description
}
-(void)viewDoubleTapped:(UITapGestureRecognizer *)tapGR{
    [_inputField resignFirstResponder]; // upon double-tap dismiss picker
}
-(void)textFieldBeganEditing:(NSNotification *)note{
    _inputField = note.object; // set ivar to current first responder
}
-(void)textFieldEndedEditing:(NSNotification *)note{
    _inputField = nil; // the first responder ended editing CRITICAL:avoids retain cycle
}
// INITI METHODS
-(void)initializationCodeMethod{
    _datePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 0, 320, 0)];// All pickers have preset height
    self.bounds = _datePicker.frame; // Make our view same size as picker
    [self addSubview:_datePicker];
    [_datePicker addTarget:self action:@selector(pickerValueChanged:) forControlEvents:UIControlEventValueChanged]; // register to be notified when the value changes
        // As an example we'll use a tap gesture recognizer to dismiss on a double-tap
    UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(viewDoubleTapped:)];
    tapGR.numberOfTapsRequired = 2; // Limit to double-taps
    [self addGestureRecognizer:tapGR];
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(textFieldBeganEditing:) name:UITextFieldTextDidBeginEditingNotification object:nil]; // Ask to be informed when any textfield begins editing
    [center addObserver:self selector:@selector(textFieldEndedEditing:) name:UITextFieldTextDidEndEditingNotification object:nil]; // Ask to be informed when any textfield ends editing
}
-(id)init{
    if ((self = [super init])){
        [self initializationCodeMethod];
    }
    return self;
}
-(id)initWithFrame:(CGRect)frame{
    if ((self = [super initWithFrame:frame])){
        [self initializationCodeMethod];
    }
    return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
    if ((self = [super initWithCoder:aDecoder])){
        [self initializationCodeMethod];
    }
    return self;
}
-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidBeginEditingNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidEndEditingNotification object:nil];
}
@end

Then in view did load you would use this class like this:

ExampleBasicDateInputView *dateEntryView = [[ExampleBasicDateInputView alloc] init];
self.dueDate.inputView = datePickerView;

And as you see in the .h file we've exposed the date picker instance as a property so you can set the style. etc. like so:

dateEntryView.datePicker.datePickerMode = UIDatePickerModeDate;

Obviously this is a very basic example, but I think it shows what can be done.

Solution 2

I had the same issue using Storyboard. Here is how I resolved it:

Overview: I alloc and initialize my UIDatePicker property in viewDidLoad:. I then set the inputView property of my textfield to my datepicker property.

Here is my code: In TripViewController.h:

@property (nonatomic,strong) IBOutlet UIDatePicker *datePicker;

In TripViewController.m:

@synthesize datePicker;
...
-(void)viewDidLoad {
...
self.datePicker = [[UIDatePicker alloc]init];
[self.datePicker addTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];
self.dateField.inputView = self.datePicker;
}

I then implement my method, dateChanged so that whenever the date picker wheel stops moving, my textfield is updated.

- (void)dateChanged
{
NSDate *date = self.datePicker.date;
NSDateFormatter *dateFormat = [[NSDateFormatter alloc]init];
[dateFormat setDateStyle:NSDateFormatterMediumStyle];
self.dateField.text = [dateFormat stringFromDate:date];
}

Solution 3

add UITextField's delegate in .h file to call this function when touched. then use the text field's tag value to determine what you want to do when the field is selected.

-(BOOL) textFieldShouldBeginEditing:(UITextField *) textField 
{
    activeTextField = textField;

    if (textField.tag == 1) 
    {
        self.datePicker.datePickerMode = UIDatePickerModeDate;
        self.isDatePicker = TRUE;
        self.tempTextField = textField;
        [self showPicker];
        return NO;
    }
    else if (textField.tag == 2) 
    {
        self.datePicker.datePickerMode = UIDatePickerModeTime;
        self.isDatePicker = TRUE;
        self.tempTextField = textField;
        [self showPicker];

        return NO;
    }
    else if (textField.tag == 3)
    {
        self.isDatePicker = FALSE;
        self.tempTextField = textField;
        [self showPicker];
        return NO;
    }
    else
    {
        self.tempTextField = textField;
        return YES;
    }
}

Solution 4

It is very easy. Defines delegate TextField (UITextFieldDelegate) in the .h in the viewDidLoad .m

myTextField.setDelegate = self;

In the same viewDidLoad associates the datePicker to myTextField.

myTextField.inputView = myDatePicker;

You can also do anime, but this is the easiest way.

Share:
34,166
Ami Schreiber
Author by

Ami Schreiber

Ami Schreiber Senior Software Developer Skills Summary I am a career software developer and overall tech enthusiast. I specialize in Microsoft technologies based on the .NET framework with a strong background in web and database design/development. I have also worked extensively with Java, Oracle, Linux and SharePoint. Key Skills and Domain Expertise Enterprise software development for Microsoft .NET platform. Databases SQL Server Oracle DB2 MySQL MS Access Operating Systems Windows (all versions) Windows Server (all versions) Linux (Redhat/Ubuntu) Mac OSX iOS Software/System Architecture Software design patterns Client-Server Three-Tier Structured SOA Programming Languages &amp; Tools ASP/ASP.NET VB/VB.NET C# Linq Java, ANT Objective-C AJAX JavaScript jQuery HTML CSS XML/XSLT PowerShell REST/Web Services T-SQL PL/SQL Enterprise Software SharePoint Server MS Exchange Server Microsoft SQL Server Visual Studio Cloud Azure Amazon EC2 Office Productivity Tools Microsoft Office (all versions) Microsoft Project

Updated on February 22, 2020

Comments

  • Ami Schreiber
    Ami Schreiber about 4 years

    I have already researched this topic to death and found people posting the exact same question on a number of websites including right here in stackoverflow.

    I have tried all of the suggestions but have been unable to get the UIDatePicker to actually display. It doesn't seem to matter what approach I take. I've tried using the inheritance model where I subclass the UITextBox control and override it's default methods in order to display the UIDatePicker and then I make sure that in StoryBoard I set the class of my UITextView control to be that custom class. I've tried programmatically generating the UIDatePicker and also tried dragging it onto the view in StoryBoard. When I try the programmatic approach nothing is displayed and when I try dragging it onto the StoryBoard it ALWAYS displays. When I set it's attribute to "hidden" it hides but then I can't get it to show even when I try to add code to the textboxDidBeginEditing method that should unhide it. I've made sure to set the UITextView's inputView property equal to my UIDatePicker control.

    Nothing works! I don't understand why Apple didn't just make the UIDatePicker one of the default options in the drop down list in the Attributes Inspector for the UITextView control. That would have made this so much easier.

    Below is some of the code from my implementation class file.

    #import "AddTasksViewController.h"
    
    @implementation AddTasksViewController
    
    @synthesize dueDate;
    @synthesize description;
    @synthesize shortTitle;
    @synthesize datePicker;
    
    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    {
        self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
        if (self) {
            // Custom initialization
            self.dueDate.inputView = datePicker;
    
        }
        return self;
    }
    
    - (IBAction)dueDateDidBeginEditing:(UITextView *)textField 
    {  
        [textField resignFirstResponder];
        [datePicker setFrame:CGRectMake(0,200,320,120)];
        [datePicker addTarget:self action:@selector(done) forControlEvents:UIControlEventValueChanged];
        [self.view addSubview:datePicker];
    }
    

    Here is what my header file looks like...

    #import <UIKit/UIKit.h>
    
    @interface AddTasksViewController : UIViewController
    
    @property (nonatomic, copy) IBOutlet UITextView      *dueDate;
    @property (nonatomic, copy) IBOutlet UITextView      *description;
    @property (nonatomic, copy) IBOutlet UITextView      *shortTitle;
    @property (nonatomic, retain) IBOutlet UIDatePicker    *datePicker;
    
    - (IBAction)doneEditing:(id)sender;
    - (IBAction)dateChanged:(id)sender;
    - (IBAction)dueDateDidBeginEditing:(UITextView *)textField;
    
    @end
    

    As I've already mentioned, the other approach that I took (i.e. subclassing UITextView) didn't work either. I copied the code provided here exactly as it was show. I then added the custom classes called "DateField" to my project (both the header and implementation files) and then replaced the UITextBox declaration for the dueDate with DateField in both my AddTaskViewController.h and AddTaskViewController.m files and made sure that the StoryBoard references were updated to by selecting DateField for the control's class in the Identity Inspector.

    No matter what I do I cannot get the UIDatePicker to display upon clicking on the UITextView. When I set a breakpoint it does hit the dueDateDidBeginEditing method so I know that something is being triggered. The problem is that I don't understand why a subView is not being showing with the UIDatePicker.

    Any help would be greatly appreciated as this seemingly straightforward task seems to be taking much, much longer than it should. I could do this sort of stuff with my eyes closed and hands tied behind my back in Java and C# yet everything is so unnecessarily complicated when it comes to Objective-C. The syntax really irritates me.

    So many square brackets!!! Arghhhh!!!