The following method takes input from a UITextField and formats it for display. This code has worked flawlessly for years, but a problem was just reported on the iPhone 6 Plus using iOS 8.1. It happens every time for the user but I have not been able to reproduce it. I believe it has to do with NSNumber/NSDecimalNumber conversions and formatting on iOS 8, perhaps for a 64-bit app/device.
The keyboard used for input is a number pad, so the only text that can be entered into the textfield are the numbers 0-9 and "delete".
According to the user, this is what is happening:
I am trying to enter a budget amount of $250. When I pull it up initially is shows 0.00. Then as soon as I enter 2, it then show 220.02 then when I enter the 5 it says 2,200.25 then when I enter the 0 it comes up with 22,002.50 if I try to erase any numbers it come up with a really large number.
The code below works perfectly with iOS 8.1, as far as I have tested, on every device in the simulator, including the iPhone 6 Plus. It also works in the iPhone 5S device (64-bit) with iOS 8.1. I do not have an iPhone 6 Plus device.
Am I missing something that someone sees might be causing this error?
EDIT: Could this possibly be because decimalNumberWithMantissa parameter should be unsigned long long and I am using NSInteger? Would this cause the problem, and if so, why has it worked until iOS 8.1 on iPhone 6 Plus? I would check this myself if I could...
The entryField UITextField is initialized as follows:
entryField.text = [NSString stringWithFormat:@"%@", [[[ObjectsHelper sharedManager] currencyFullFormatter] stringFromNumber:[NSDecimalNumber zero]]];
and here is the rest of the relevant code:
#define MAX__NUMBER_LENGTH 10
- (BOOL)textField:(UITextField *)textFieldHere shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
__ENTERING_METHOD__
NSMutableString *mstring = [[NSMutableString alloc] initWithString:[entryField text]];
if([string length] > 0){
//add case
[mstring insertString:string atIndex:range.location];
}
else {
//delete case - the length of replacement string is zero for a delete
[mstring deleteCharactersInRange:range];
}
NSString *clean_string = [[mstring componentsSeparatedByCharactersInSet:
[[NSCharacterSet decimalDigitCharacterSet] invertedSet]]
componentsJoinedByString:@""];
//clean up mstring since it's no longer needed
if ((clean_string.length >= MAX__NUMBER_LENGTH && range.length == 0) || ([clean_string length] == 0 && [string isEqualToString:@"0"]))
{
return NO; // return NO to not change text
}
else {
//get the cleaned price in the form of a NSNumber - it has not yet been scaled
NSNumber *priceNumberBeforeScale = [[DateHelper decimalFormatter] numberFromString:clean_string];
self.budgetIntNumber = priceNumberBeforeScale;
//get the cleaned price in the form of an integer - it has not yet been scaled
NSInteger priceIntBeforeScale = [priceNumberBeforeScale integerValue];
//scale the price for currency
NSDecimalNumber *priceScaled = [NSDecimalNumber decimalNumberWithMantissa:priceIntBeforeScale exponent:(0-[[[ObjectsHelper sharedManager] currencyScale] integerValue]) isNegative:NO];
//now format the price for currency
//and get the grouping separators added in and put it in the UITextField
entryField.text = [[[ObjectsHelper sharedManager] currencyFullFormatter] stringFromNumber:priceScaled];
//always return no since we are manually changing the text field
return NO;
}
}
From DateHelper.m:
+ (NSNumberFormatter *)decimalFormatter {
__ENTERING_METHOD__
NSNumberFormatter *decimalFormatter = [[NSNumberFormatter alloc] init];
[decimalFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[decimalFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
return decimalFormatter;
}
From ObjectsHelper.m:
- (NSNumberFormatter*)currencyFullFormatter {
__ENTERING_METHOD__
if (currencyFullFormatter != nil) {
return currencyFullFormatter;
}
currencyFullFormatter = [[NSNumberFormatter alloc] init];
[currencyFullFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[currencyFullFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
return currencyFullFormatter;
}
- (NSNumber*)currencyScale {
__ENTERING_METHOD__
if (currencyScale != nil) {
return currencyScale;
}
self.currencyScale = [NSNumber numberWithInteger:[[[ObjectsHelper sharedManager] currencyFullFormatter] maximumFractionDigits]];
return currencyScale;
}
EDIT: Seems like this answer might be on the right track, just not exactly sure how that would translate here. Would changing
//get the cleaned price in the form of an integer - it has not yet been scaled
NSInteger priceIntBeforeScale = [priceNumberBeforeScale integerValue];
//scale the price for currency
NSDecimalNumber *priceScaled = [NSDecimalNumber decimalNumberWithMantissa:priceIntBeforeScale exponent:(0-[[[ObjectsHelper sharedManager] currencyScale] integerValue]) isNegative:NO];
to
//scale the price for currency
NSDecimalNumber *priceScaled = [NSDecimalNumber decimalNumberWithMantissa:[priceNumberBeforeScale unsignedLongLongValue] exponent:(0-[[[ObjectsHelper sharedManager] currencyScale] integerValue]) isNegative:NO];
be likely to solve the problem?
Seems like there may be an issue with custom keyboards for iOS 8. Need to investigate further but at this point at least one occurrence of this issue has been fixed by removing the custom keyboard from the device.
EDIT: Is it confirmed that this particular issue was caused by an iOS 8 custom keyboard app. The issue has been reported to the publishers of the app.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With