How to hide the cursor in a UITextField when using a UIDatePicker/UIPickerView

10/13/2012 – Since the first version of this post, I’ve put the subclassed UILabel up on Github. It comes complete with a working demo for the iPhone and iPad.

A UITexField is a native iOS object that allows a user to input text with a keyboard. However, in some cases what you really want to do is to allow only a discrete number of text inputs.

The first thing you may try is to bring up a UIDatePicker or a UIPicker instead of the normal keyboard when the user taps on a text field. Then, you could try changing the UITextField‘s value based on what the user selects on one of these pickers.

This is accomplished by rewriting a UITextField‘s inputView property.


//Create a UIDatePicker
UIDatePicker datePicker = [[UIDatePicker alloc] init];
datePicker.datePickerMode = UIDatePickerModeDate;
[datePicker addTarget:self action:@selector(datePickerChanged:) forControlEvents:UIControlEventValueChanged];

//Set the textField's inputView to be the UIPicker
self.textField.inputView = datePicker;

But then you run into this problem:

The cursor is still there. This may seem like a small problem but it is really, really bad. The user could use the cursor to copy-paste whatever he or she wants in there. This defeats the purpose of limiting possible inputs and can even corrupt your data if you are sending this input to a backend server.

Then there is the UI design problem. The user is confused. Do you want me to type or do you want me to choose? Keeping the cursor there is sloppy.

There may be other ways around this problem, including hiding the UITextField behind another UITextField, using invisible buttons, disabling copy-paste functionality, etc… These all feel like they are cool little hacks (I couldn’t get any of them to work) but the way I’m proposing here is simple, elegant and object-oriented. It’s a UILabel subclass that I’m calling PRLabel until I can find a better name for it.

Header file:


#import <UIKit/UIKit.h>

@interface PRLabel : UILabel

@property (strong, nonatomic, readwrite) UIView* inputView;
@property (strong, nonatomic, readwrite) UIView* inputAccessoryView;

@end

Implementation file:


#import "PRLabel.h"

@implementation PRLabel

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}

- (BOOL)isUserInteractionEnabled
{
return YES;
}

- (BOOL)canBecomeFirstResponder
{
return YES;
}

# pragma mark - UIResponder overrides

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self becomeFirstResponder];
}

@end

PRLabel overrides five things from the original UILabel. The first two are the properties inputView and inputViewAccessory. These properties are inherited from the UIResponder and they determine what views (keyboard, date picker, etc) come up when a UIResponder object becomes firstResponder.

Both of these properties are read-only by default in a UILabel so we have to override their pointers in the implementation file (e.g. @property (strong, nonatomic, readwrite) UIView* inputView).

Third, you need to override the getter method isUserInteractionEnabled to always return YES. This property determines whether or not a UI object should respond to touch events. The default is NO for the UILabel class because static text is not supposed to be interactive.

Fourth, UILabel cannot become firstResponder by default. The inputView only comes up when an object becomes first responder so we also need to override the method canBecomeFirstResponder in our UILabel subclass so that it always returns YES.

Finally, we need to override the touchesEnded method that is inherited from UIResponder so that the PRLabel object becomes first responder by default when it detects a touch. This can also be done with a gesture recognizer, but doing it this way maintains a clean UIResponder chain.

After all these changes have been made, you can use your new PRLabel instead of a UITextField and you won’t have to worry about the cursor when you hook it up to a picker.

, ,

  • pietrorea

    Did you override the inputView property correctly? The pointer should read “@property (strong, nonatomic, readwrite) UIView* inputView”. This is a bit different from what the first version of this post said. Please refer to the github project linked on top. It has a working demo.

  • Andy

    Thank you! This is perfect.

  • Zhivotnoe

    God Bless You!
    Thanks a lot, man!

  • Bryan

    This is fantastic. One other thing: If you want it to act like a UITextField and keep the superview from intercepting touches, implement an empty touchesBegan:withEvent: method in the PRLabel class. This is particularly important if the superview uses touchesBegan:withEvent: to dismiss the keyboard.

    • Chan

      nice tip Bryan, already implemented empty touchesBegan method in PRLabel class and that fixed showing the inputView which s occured after touching outside tableview etc. But i cant still dismiss the keyboard via the done button in accessoryInputView. I was dismissing it with [view endEditing:YES] method, but it doesnt work for PRLabel inputViews.