I sometimes use a little trick to ensure that a UITextInputField only accepts a certain subset of characters. Say for example, you want to ensure that a user enters only letters and spaces. A UITextField delegate can catch each character as its typed and decide whether to add items to the active text field. Here's how.
Start by defining a legal set of characters. The following constant string includes the alphabet, both lower and upper case, plus the space character:
#define LEGAL @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz "
A simple delegate method can check whether a newly typed character belongs to that legal set. If so, it permits the text field to add it:
// allow only legal letters
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:LEGAL] invertedSet];
NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
return [string isEqualToString:filtered];
}
It's relatively easy to extend this concept to limit text input to well formed numbers. Start by using a number keyboard:
entryField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
Next, define two number sets: one with the period, one without.
#define NUMBERS @"0123456789" #define NUMBERSPERIOD @"0123456789."
Finally, adjust the text field delegate method to check to see if a period has already been added. If so, the test reverts to the numbers only string:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSCharacterSet *cs;
NSString *filtered;
// Check for period
if ([entryField.text rangeOfString:@"."].location == NSNotFound)
{
cs = [[NSCharacterSet characterSetWithCharactersInString:NUMBERSPERIOD] invertedSet];
filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
return [string isEqualToString:filtered];
}
// Period is in use
cs = [[NSCharacterSet characterSetWithCharactersInString:NUMBERS] invertedSet];
filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
return [string isEqualToString:filtered];
}
It's pretty easy to see how you can extend this approach to allow for legal email addresses, user names, passwords and so forth. Just this small bit of code can save you a lot of overhead. You can avoid checking for legal entries and making users enter items again when you ensure that users can only enter legal values to begin with.
Nice!
Thanks for that little chunk-o-code as I have an up and coming use for it myself.
Wouldn't it be easier and better to use regexes rather than checking against a string?
Hang, regular expressions on the iPhone require a third party framework as there's no native solution. So slightly trickier, but yes, that's the usual solution for this type of problem.
Wouldn't it be easier and better to use NSScanner rather than using the componentsXXX methods to copy the string to an array then copy back to a string?
After all, searching a string with a NSCharacterSet is what NSScanner is for.
Hi Erica,
Nice and helpful trick. I have bookmarked this at http://www.iphonekicks.com/tipsandtricks/Defining_legal_input_characters
Thanks for all your nice posts ;-)
Nike Dunk shoes
nike star shoes
This worked really well and stopped the entry of non desirable characters however it also stopped the keyboard from dismissing in the following method.
- (IBAction)textFieldDoneEditing:(id)sender
{
[sender resignFirstResponder];
}
How would I get the textFieldDoneEditing method to be called before the delegate method?
Or what key stroke would I use to add the "textFieldDoneEditing" into the legal characters constant?
Thanks In Advance
p.s. Love your cookbook Erica
Worked it out
I replaced
- (IBAction)textFieldDoneEditing:(id)sender
{
[sender resignFirstResponder];
}
with
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
Then deleted the links to textFieldDoneEditing in the xib
Hello, I tried using this code in a current iPhone application that I'm working on (also my first iPhone app), and I'm having a lot of trouble applying this to multiple text fields, or even limiting it to certain text fields. Can anyone explain how that would work? I've tried this:
// Check for period
if ([txt1.text rangeOfString:@"."].location == NSNotFound && [txt2.text rangeOfString:@"."].location == NSNotFound)
{
cs = [[NSCharacterSet characterSetWithCharactersInString:NUMBERSPERIOD] invertedSet];
filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
return [string isEqualToString:filtered];
}
And also this:
// Check for period
if ([txt1.text rangeOfString:@"."].location == NSNotFound || [txt2.text rangeOfString:@"."].location == NSNotFound)
{
cs = [[NSCharacterSet characterSetWithCharactersInString:NUMBERSPERIOD] invertedSet];
filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
return [string isEqualToString:filtered];
}
With the first one, it'll work for the first text field if I enter the numbers there first, but the 2nd one will not let me put a period. The second version will let me put as many periods as I want in the first textfield but only 1 in the second text field.
I figured out how to get it to work with multiple text boxes. It was actually really simple. I changed these lines:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
// Check for period
if ([entryField.text rangeOfString:@"."].location == NSNotFound)
To this:
- (BOOL)textField:(UITextField *)textFieldBeingChanged shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
// Check for period
if ([textFieldBeingChanged.text rangeOfString:@"."].location == NSNotFound)
I'm still not sure how to get it to apply to specific text fields though (like if I want a text field on the view that "can" have letters on it, but also have 1 that should only allow numbers).
Thanks for the great info on how to do this though. This was almost exactly what I needed (minus what I already mentioned).
I wasn't even looking for this, but it's perfect. Trying to remember what I _was_ looking for, but no matter. Thank you =)
It's pretty easy to see how you can extend this approach uggs sale to allow for legal email addresses, user names, passwords and so forth. Just this small bit of code can save you a lot of overhead. You can avoid checking for legal entries and making users enter items again when you ensure that users can only enter legal values to begin with.