So here is the situation of the timing. I have a UILabel that I want to update every time the keyboard updates a UITextField. I have two UITextFields but only one is ever the first responder so don't worry about there being two I have them for back end purposes. The problem is the timing from the UILabel updating and the UITextField delegate function
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;˚
The replacement string doesn't get added until YES is returned to by the above function. I need to update my labels either after this function is called or during this function. I can't seem to figure out how it will work. The UILabel is always one character behind. Below is my code in general for this section.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if([self.hiddenTextFieldForTimeMinutes.text length] == 2 && [self.hiddenTextFieldForTime.text length] == 2 && ![string isEqualToString:#""])
{
return NO;
}
[self syncTextFieldsMinutesAndHours: string];
// This returns the default Yes;
return YES;
}
- (void) setAccessoryLabel: (NSString *) hourString minutesString: (NSString *) minuteString
{
timeAccessoryLabel.text = [NSString stringWithFormat:#"%#:%#", hourString, minuteString];
}
- (void) syncTextFieldsMinutesAndHours: (NSString *) string
{
// These are the textFields
NSMutableString *hoursString = [NSMutableString stringWithFormat: #"%#", self.hiddenTextFieldForTime.text];
NSMutableString *minutesString = [NSMutableString stringWithFormat: #"%#", self.hiddenTextFieldForTimeMinutes.text];
if([self.hiddenTextFieldForTimeMinutes.text length] == 2 && ![string isEqualToString: #""])
{
[hoursString appendString: [NSString stringWithFormat:#"%c", [minutesString characterAtIndex:0]]];
[self.hiddenTextFieldForTime setText:[NSString stringWithFormat:#"%#", hoursString]];
[self.hiddenTextFieldForTimeMinutes setText: [self.hiddenTextFieldForTimeMinutes.text substringFromIndex:1]];
} else if([self.hiddenTextFieldForTimeMinutes.text length] == 2 && [string isEqualToString: #""])
{
// Hours has nothing in it
if([hoursString length] == 0)
{
return;
} else if([hoursString length] == 1)
{
// Since the timing of the add and remove of the string is done by return of the delegate we append the string to the beginning first then return.
[self.hiddenTextFieldForTimeMinutes setText: [NSString stringWithFormat:#"%c%#", [self.hiddenTextFieldForTime.text characterAtIndex:0], self.hiddenTextFieldForTimeMinutes.text]];
[self.hiddenTextFieldForTime setText:#""];
} else if ([hoursString length] == 2)
{
// Since the timing of the add and remove of the string is done by return of the delegate we append the string to the beginning first then return.
[self.hiddenTextFieldForTimeMinutes setText: [NSString stringWithFormat:#"%c%#", [self.hiddenTextFieldForTime.text characterAtIndex:1], self.hiddenTextFieldForTimeMinutes.text]];
[self.hiddenTextFieldForTime setText: [NSString stringWithFormat:#"%c", [self.hiddenTextFieldForTime.text characterAtIndex:0]]];
}
}
[self setAccessoryLabel: self.hiddenTextFieldForTime.text minutesString:self.hiddenTextFieldForTimeMinutes.text];
}
yes. The text of the textField in textField:shouldChangeCharactersInRange:replacementString: will still have the old value, because it only gets changed after you answered yes to the question if the text should change.
You have two options.
create the NSString that your textField will have after you returned YES yourself:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if([self.hiddenTextFieldForTimeMinutes.text length] == 2 && [self.hiddenTextFieldForTime.text length] == 2 && ![string isEqualToString:#""])
{
return NO;
}
NSString *realString = [textField.text stringByReplacingCharactersInRange:range withString:string];
[self syncTextFieldsMinutesAndHours: realString];
// This returns the default Yes;
return YES;
}
or add a IBAction that gets called after the editing took place:
- (void)viewDidLoad {
// your viewDidLoad implementation
[textField addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
}
- (IBAction)textFieldDidChange:(UITextField *)sender {
[self syncTextFieldsMinutesAndHours: sender.text];
}
After mulling for a few minutes, this could be a run loop issue. Try adding this before you call the method to update your UILabel:
[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
//update label
[self updateLabelWithText:foo andText:bar];
Or try using GCD:
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
dispatch_async(main_queue, ^{
// UI Updates here
[self updateLabel...];
});
});
I want to have runtime validation on my UITextField, i.e. when user starts entering data, it should validate and pop up error message if not correct. How do I implement this feature ?
textfieldDidChange or textFieldShouldEndEditing can be of any help ?
Any tutorial ?
You can implement the textField:shouldChangeCharactersInRange:replacementString: method on your text field's delegate, rejecting any characters that are invalid for your textfield. See the UITextFieldDelegate protocol reference for more information.
For example, if you only want to allow entry of decimal numbers, you could use:
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSCharacterSet *nonNumberSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
return ([string stringByTrimmingCharactersInSet:nonNumberSet].length > 0);
}
This won't actually pop up an error message if the input is incorrect; instead, it will prevent entry of characters that are incorrect.
If you do want to allow the user to type invalid characters, and them give them an error, you could implement this in the textFieldShouldEndEditing: method of your delegate.
This is a better solution since it allows backspaces.
Taken from :Hugo Larcher's blog
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSCharacterSet *nonNumberSet;
if (textField == self.priceField) //allow decimals
nonNumberSet=[[NSCharacterSet characterSetWithCharactersInString:#"0123456789."] invertedSet];
else
nonNumberSet=[[NSCharacterSet characterSetWithCharactersInString:#"0123456789"] invertedSet];
// allow backspace
if (range.length > 0 && [string length] == 0) {
return YES;
}
// do not allow . at the beggining
if (range.location == 0 && [string isEqualToString:#"."]) {
return NO;
}
// set the text field value manually
NSString *newValue = [[textField text] stringByReplacingCharactersInRange:range withString:string];
newValue = [[newValue componentsSeparatedByCharactersInSet:nonNumberSet] componentsJoinedByString:#""];
textField.text = newValue;
// return NO because we're manually setting the value
return NO;
}
i want to validate a text field to accept only characters while typing in it.....
I use the following for integers but I guess you could easily modify it to scan for strings/chars (see NSScanner):
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *resultingString = [textField.text stringByReplacingCharactersInRange: range withString: string];
// This allows backspace
if ([resultingString length] == 0) {
return true;
}
NSUInteger holder;
NSScanner *scan = [NSScanner scannerWithString: resultingString];
return [scan scanInteger: &holder] && [scan isAtEnd];
}
Don't forget to set the UITextField delegate appropriately :)
i want to restrict user from entering space in a UITextField. for this i m using this code
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ( string == #" " ){
UIAlertView *error = [[UIAlertView alloc] initWithTitle:#"Error" message:#"You have entered wrong input" delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[error show];
return NO;
}
else {
return YES;
}
}
but it is not working .... what is wrong in it ?
The problem is
string == #" "
is wrong. Equality for strings is done using:
[string isEqualToString:#" "]
:).
This will search to see if your replacement string contains a space, if it does then it throws the error message up, if it doesn't it returns YES.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSRange spaceRange = [string rangeOfString:#" "];
if (spaceRange.location != NSNotFound)
{
UIAlertView *error = [[UIAlertView alloc] initWithTitle:#"Error" message:#"You have entered wrong input" delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[error show];
return NO;
} else {
return YES;
}
}
The current answer is this:
Set the View Controller to conform to the UITextFieldDelegate (should look something like this near the top of your code):
#interface YourViewController () <UITextFieldDelegate>
...
# end
# implementation YourViewController
...
#end
Then make the textfield use the View Controller as its delegate. Do this by going to the Interface Builder, control clicking on the textfield and dragging a line to the yellow circle on the bar underneath the View Controller, and selecting "delegate" from the menu that pops up. You could alternatively do this in code by setting the delegate after making the property described in the next paragraph. Do it this way with self.yourTextField.delegate = self; in an appropriate place, possibly in viewDidLoad.
Also set the textField up as a property on the View Controller. Do this by going to the Interface Builder, with its code open in the assistant editor, and control click and drag from the text field in the Interface Builder, to the place in the code where the properties are listed (between #interface and the first #end). Then enter a name in the pop up window. In the code below I used "yourTextField" for example. (you can skip this section, together with the outside if loop in the code below if you are sure that this is the only text field that will use the View Controller as its delegate, but it is best to plan ahead for future possibilities)
Then you can disallow spaces from even be entered using the following delegate method:
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (textField == self.yourTextField)
{
if ([string isEqualToString:#" "] )
{
return NO;
}
}
return YES;
}
Try this (set this in your - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string):
NSArray *escapeChars = [NSArray arrayWithObjects:#" ", nil];
NSArray *replaceChars = [NSArray arrayWithObjects:#"",nil];
int len = [escapeChars count];
NSMutableString *temp = [[textField text] mutableCopy];
for(int i = 0; i < len; i++) {
[temp replaceOccurrencesOfString: [escapeChars objectAtIndex:i] withString:[replaceChars objectAtIndex:i] options:NSLiteralSearch range:NSMakeRange(0, [temp length])];
}
[textField setText:temp];
return TRUE;
string == #" "
Isn't that just going to compare the adress of each of string and #" "? Thats not the comparison you want to do.
Also do you want to prevent them from entering a string that is just a space? If so then you need to change that == into a proper string comparison and you are good to go. If not and you want to prevent all spaces in an input string then you need a string matcher
Below is what I am using for password and confirm password
In PrefixHeader.pch file add below
#define NONACCEPTABLE_PASSWORD_CHARACTERS #" "
And in code use below.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField==passwordTF || textField==confirmPasswordTF) {
NSCharacterSet *cs = [NSCharacterSet characterSetWithCharactersInString:NONACCEPTABLE_PASSWORD_CHARACTERS];
NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:#""];
return [string isEqualToString:filtered];
}
return YES;
}
My application allows the user to enter a numeric value (currency) in a UITextField control, but the keyboard layout that I wish was available is unfortunately not one of the built-in options, so I had to choose the "Numbers & Punctuation" option in Interface Builder. Here's the corresponding dialog window in IB:
So when my application asks the user for the input, it is displaying the following:
Which is perfectly fine, but look at all of the extra keys available to the user! One could easily enter "12;56!" in the text field, and I assume I have to validate that somehow.
So my question is: how do I validate currency values entered into a UITextField?
I have the urge to answer because this was the first entry I saw when I googled and the highest ranked answer wouldn't allow me to enter a currency.
I'm german, and in germany (and in many other countries) we use , as the decimalseparator.
I just wrote a similar method and this is what I have right now.
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
static NSString *numbers = #"0123456789";
static NSString *numbersPeriod = #"01234567890.";
static NSString *numbersComma = #"0123456789,";
//NSLog(#"%d %d %#", range.location, range.length, string);
if (range.length > 0 && [string length] == 0) {
// enable delete
return YES;
}
NSString *symbol = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator];
if (range.location == 0 && [string isEqualToString:symbol]) {
// decimalseparator should not be first
return NO;
}
NSCharacterSet *characterSet;
NSRange separatorRange = [textField.text rangeOfString:symbol];
if (separatorRange.location == NSNotFound) {
if ([symbol isEqualToString:#"."]) {
characterSet = [[NSCharacterSet characterSetWithCharactersInString:numbersPeriod] invertedSet];
}
else {
characterSet = [[NSCharacterSet characterSetWithCharactersInString:numbersComma] invertedSet];
}
}
else {
// allow 2 characters after the decimal separator
if (range.location > (separatorRange.location + 2)) {
return NO;
}
characterSet = [[NSCharacterSet characterSetWithCharactersInString:numbers] invertedSet];
}
return ([[string stringByTrimmingCharactersInSet:characterSet] length] > 0);
}
While you can putz around in the NSNumberFormatter, I found it easier to just screen out anything but 0-9 and .
This is working well for me:
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSCharacterSet *nonNumberSet = [[NSCharacterSet characterSetWithCharactersInString:#"0123456789."] invertedSet];
return ([string stringByTrimmingCharactersInSet:nonNumberSet].length > 0);
}
I found this to be a relatively clean approach. I haven't tested it with non-US currencies but since it uses the NSNumberFormatter's properties I believe it should handle them correctly.
Start by setting up a formatter some place:
formatter = [NSNumberFormatter new];
[formatter setNumberStyle: NSNumberFormatterCurrencyStyle];
[formatter setLenient:YES];
[formatter setGeneratesDecimalNumbers:YES];
Use the formatter to parse and reformat their input. It also handles shifting the number when digits are added and removed.
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *replaced = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSDecimalNumber *amount = (NSDecimalNumber*) [formatter numberFromString:replaced];
if (amount == nil) {
// Something screwed up the parsing. Probably an alpha character.
return NO;
}
// If the field is empty (the initial case) the number should be shifted to
// start in the right most decimal place.
short powerOf10 = 0;
if ([textField.text isEqualToString:#""]) {
powerOf10 = -formatter.maximumFractionDigits;
}
// If the edit point is to the right of the decimal point we need to do
// some shifting.
else if (range.location + formatter.maximumFractionDigits >= textField.text.length) {
// If there's a range of text selected, it'll delete part of the number
// so shift it back to the right.
if (range.length) {
powerOf10 = -range.length;
}
// Otherwise they're adding this many characters so shift left.
else {
powerOf10 = [string length];
}
}
amount = [amount decimalNumberByMultiplyingByPowerOf10:powerOf10];
// Replace the value and then cancel this change.
textField.text = [formatter stringFromNumber:amount];
return NO;
}
Perhaps you could attach a UITextFieldDelegate on the control and have it implement textField:shouldChangeCharactersInRange:replacementString: That way, if it sees any characters that you don't want in the field, it can reject them.
In conjunction with the textField:shouldChangeCharactersInRange:replacementString: suggestion made by Marc, you should pass the text through an NSNumberFormatter using an NSNumberFormatterCurrencyStyle. This will handle the quirks of currency formatting and handle locale specific options.
There's a "Data Formatting Programming Guide for Cocoa" section in the iPhone documentation if you search for it. Sadly, most of the UI information here is Mac OS X specific (doesnt work on iPhone) but it'll show you how to use the formatter classes.
I found that the shouldChangeCharactersInRange screws up the pop-up keyboard, backspace and "Done" button as well. I found if I handled 0 length strings and allowed control characters though, it worked fine.
I don't like using NSNumberFormatter because it insists that the number is well-formed at all stages while the user is editing and that can be infuriating if you, say, want to have two decimal points in the number for a moment until you delete the one that's in the wrong spot.
Here's the code I used:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if ([string length] < 1) // non-visible characters are okay
return YES;
if ([string stringByTrimmingCharactersInSet:[NSCharacterSet controlCharacterSet]].length == 0)
return YES;
return ([string stringByTrimmingCharactersInSet:[self.characterSet invertedSet]].length > 0);
}
Where self.characterSet holds the characters that are acceptable, I used this method to create it for a currency:
- (NSCharacterSet *)createCurrencyCharacterSet
{
NSLocale *locale = [NSLocale currentLocale];
NSMutableCharacterSet *currencySet = [NSMutableCharacterSet decimalDigitCharacterSet];
[currencySet addCharactersInString:#"-"]; // negative symbol, can't find a localised version
[currencySet addCharactersInString:[locale objectForKey:NSLocaleCurrencySymbol]];
[currencySet addCharactersInString:[locale objectForKey:NSLocaleGroupingSeparator]];
[currencySet addCharactersInString:[locale objectForKey:NSLocaleDecimalSeparator]];
return [[currencySet copy] autorelease];
}
The somewhat unhappy code [[currencySet copy] autorelease] returns an immutable NSCharacterSet.
Using [NSCharacterSet decimalDigitCharacterSet] also includes the Indic and Arabic equivalent characters which hopefully means that people use those languages can use their alphabet's digits to enter numbers.
It's still necessary to check that NSNumberFormatter can parse the user's input and alert if it can't; nonetheless, it makes a nicer experience when only legit characters can be entered.
I keep seeing that people don't know how to properly determine the resulting string when implementing shouldChangeCharactersInRange.
Here's how you get the new text that would be entered if you returned YES:
NSString *newText = [textField.text stringByReplacingCharactersInRange:range withString:string];
Once you have this new text, it's easy to check if that text is a valid number or not, and return YES or NO accordingly.
Keep in mind that all other solutions, such as those shown here, where one checks the length of "string", and such, may not work properly if the user tries to edit the string using a bluetooth keyboard with cursor keys or using the advanced editing features (select, cut, paste).
If you want them to only be able to enter numbers - you might also consider the number keypad
After finding a quick solution on stack overflow to handle US currency, I rewrote the function to safely handle international currencies as well. This solution will dynamically validate user input from a UITextField, correcting as they type.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setMaximumFractionDigits:2];
[numberFormatter setMinimumFractionDigits:0];
// Grab the contents of the text field
NSString *text = [textField text];
// the appropriate decimalSeperator and currencySymbol for the current locale
// can be found with help of the
// NSNumberFormatter and NSLocale classes.
NSString *decimalSeperator = numberFormatter.decimalSeparator;
NSString *currencySymbol = numberFormatter.currencySymbol;
NSString *replacementText = [text stringByReplacingCharactersInRange:range withString:string];
NSMutableString *newReplacement = [[ NSMutableString alloc ] initWithString:replacementText];
// whenever a decimalSeperator or currencySymobol is entered, we'll just update the textField.
// whenever other chars are entered, we'll calculate the new number and update the textField accordingly.
// If the number can't be computed, we ignore the new input.
NSRange decimalRange = [text rangeOfString:decimalSeperator];
if ([string isEqualToString:decimalSeperator] == YES &&
[text rangeOfString:decimalSeperator].length == 0) {
[textField setText:newReplacement];
} else if([string isEqualToString:currencySymbol] == YES&&
[text rangeOfString:currencySymbol].length == 0) {
[textField setText:newReplacement];
} else if([newReplacement isEqualToString:currencySymbol] == YES) {
return YES;
}else {
NSString *currencyGroupingSeparator = numberFormatter.currencyGroupingSeparator;
[newReplacement replaceOccurrencesOfString:currencyGroupingSeparator withString:#"" options:NSBackwardsSearch range:NSMakeRange(0, [newReplacement length])];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSNumber *number = [numberFormatter numberFromString:newReplacement];
if([newReplacement length] == 1) {
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
number = [numberFormatter numberFromString:newReplacement];
}
if (number == nil) {
[newReplacement release];
return NO;
}
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
text = [numberFormatter stringFromNumber:number];
[textField setText:text];
}
[newReplacement release];
return NO; // we return NO because we have manually edited the textField contents.
}
Using an NSNumberFormatter is the correct answer. It will handle validation and converting the string to and from the correct object type.
You can also use Gamma-Point solution and evaluate if *aNumber is Nil, if its Nil, then it means a character that wasnt 0-9 . or , was entered, this way you can validate just numbers, it will nil the variable if any no-number char is entered.
I Couldn't find the appropriate implementation for currency validate. Here is my variant:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString* proposedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
//if there is empty string return YES
if([proposedString length] == 0) {
return YES;
}
//create inverted set for appripriate symbols
NSCharacterSet *nonNumberSet = [[NSCharacterSet characterSetWithCharactersInString:#"0123456789.,"] invertedSet];
//if side symbols is trimmed by nonNumberSet - return NO
if([proposedString stringByTrimmingCharactersInSet:nonNumberSet].length != [proposedString length]) {
return NO;
}
//if there is more than 1 symbol of '.' or ',' return NO
if([[proposedString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#".,"]] count] > 2) {
return NO;
}
//finally check is ok, return YES
return YES;
}
Using shouldChangeCharactersInRange screws up the pop-up key board as the backspace button doesn't work.
Number formatter is the way to go. Here's a sample I used to find positive decimals. I call it during the validation check- for e.g. when the user clicks on the save button.
-(BOOL) isPositiveNumber: (NSString *) numberString {
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *aNumber = [numberFormatter numberFromString:numberString];
[numberFormatter release];
if ([aNumber floatValue] > 0) {
NSLog( #"Found positive number %4.2f",[aNumber floatValue] );
return YES;
}
else {
return NO;
}
}