iOS 8 number conversion/formatting error? (cannot reproduce)
Asked Answered
B

1

5

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?

Booster answered 4/11, 2014 at 17:22 Comment(5)
Don't know if that helps, but instead of your "add case/delete case" you should probably do [mstring replaceCharactersInRange:range withString:string] . Note that a user might select a part of the textfield and replace that via copy/paste.Mensa
Thank you. Do you believe that would cause this problem? I am sure if I could reproduce the error I could (eventually) fix it but unfortunately I am an independent developer and cannot afford to purchase the iPhone 6 Plus. Any thoughts as to why an error like this would show up every time on a device (at least one device) but never in the simulator for that device?Booster
Also, the textField is not selectable, if that helps anything (which it probably doesn't). The only way that the user can change the content is through the keyboard with keys 0-9 and delete.Booster
OK, I just noticed that when reading your question.Mensa
Is there a way I can make this question better?Booster
B
1

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.

Booster answered 11/11, 2014 at 0:58 Comment(1)
On a sidebar note - should I contact Apple about this? If third party keyboard apps can cause such a problem, should Apple be made aware of this? I am not exactly sure how such apps function, but it seemed almost as if UITextfield delegates were being overwritten in my case.Booster

© 2022 - 2024 — McMap. All rights reserved.