views:

6879

answers:

10

In my app users need to be able to enter numeric values with decimal places. The iPhone doesn't provides a keyboard that's specific for this purpose - only a number pad and a keyboard with numbers and symbols.

Is there an easy way to use the latter and prevent any non-numeric input from being entered without having to regex the final result?

Thanks!

+2  A: 

Depending on the specific application, providing a slider that the user can select a position from might be a better choice on the iphone. Then no digits need to be entered at all.

Martin v. Löwis
+2  A: 

You may want to use a slider (as suggested by Martin v. Löwis) or a UIPickerView with a separate wheel for each of the digits.

Jeroen Heijmans
+1  A: 

I wanted to do exactly the same thing, except with currencies rather than straight decimal values.

I ended up creating a custom view which contains a UITextField and a UILabel. The label covers the text field, but the text field still receives touches. I use the UITextFieldTextDidChange notification to observe changes in the text field (and I used a NSNumberFormatter to turn the resulting number into a formatted currency value) to update the label.

To disable the loupe that allows the user to reposition the insertion point, you'll need to use a custom UITextField subclass and override touchesBegan:withEvent: and set it to do nothing.

My solution might be different from what you need because the decimal point is always fixed -- I use the system's currency setting to determine how many there digits ought to be after the decimal point. However, the numeric keypad doesn't have a decimal point on it. And you can't add any buttons to the keyboard (which is especially aggravating because there's a blank button in the lower-left corner of the keyboard that would be perfect for a decimal point!) So I don't have a solution for that, unfortunately.

Alex
I've done something like this, except with a custom view. When it receives a touch I set up a text field at the bottom of the screen (behind the keyboard) and call `becomeFirstResponder` on it. Then I don't need to worry about disabling the loupe.
benzado
+19  A: 

A more elegant solution happens to also be the simplest.

You don't need a decimal separator key

Why? Because you can simply infer it from the user's input. For instance, in the US locale when you what to enter in $1.23, you start by entering the numbers 1-2-3 (in that order). In the system, as each character is entered, this would be recognized as:

  • user enters 1: $0.01
  • user enters 2: $0.12
  • user enters 3: $1.23

Notice how we inferred the decimal separator based on the user's input. Now, if the user wants to enter in $1.00, they would simply enter the numbers 1-0-0.

In order for your code to handle currencies of a different locale, you need to get the maximum fraction digits of the currency. This can be done with the following code snippet:

NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
int currencyScale = [currencyFormatter maximumFractionDigits];

For example, the Japanese yen has a maximum fraction digit of 0. So, when dealing with yen input, there is no decimal separator and thus no need to even worry about fractional amounts.

This approach to the problem allows you to use the stock numeric input keypad provided by Apple without the headaches of custom keypads, regex validation, etc.

shek
Just to make sure -- don't use floats for currency!
Svante
+1 -- use NSDecimalNumber
shek
I also wrote a blog post concerning this for anyone that is interested: http://blog.weareuproar.com/?p=11
shek
One point - unfortunately the iPhone SDK UIControl does not support a setFormatter method to allow you to attach the NSFormatter to the UITextField, or other UIControl (the equivalent Cocoa NSControl does support setFormatter). I raised an enhancement issue with Apple about this (#5847381).
Dan J
The original question doesn't say anything about currency, yet this answer pretty much assumes we want to input currency. It's not helpful if you want to accept arbitrary floating point input.
benzado
Given this answer was accepted I suppose the original question was referring to decimal numbers where the precision is fixed (of which currency is just one example).
shek
A: 

For my own app I implemented shek's suggestion (by subclassing UITextField) and now I need to tell my users about the change. How do I succintly describe this style of input?

cash-register style? I'm not sure if my users are old enough to remember such antiques.

Brandon Fosdick
A: 

noob here, trying to use the formatter which i can do if i'm just formatting, but now am trying to implement this into a uitextfield to accomplish the number pressed.

does it use the text changed in the uitextfielddelegate?

maybe i'm missing something simple, but just trying to get more on that.

thanks

A: 

Alex, can you give more details and simple example how you did that because I am lost.

Thank you!!!

+1  A: 

int currencyScale = [currencyFormatter maximumFractionDigits];

That gets me info like "USA needs 2 decimal places"... but then what do I do???

How do I change 1234 into 12.34 as the user types?

And where would I place this code?

With a dozen different keyboards on the iPhone... no one thought "oh, enter digits and decimal point" might be needed for numerical input???

And avoid the "telephone alphabet" characters altogether.

Was there a REASON why Apple didn't provide a SIMPLE "0-9 and decimal point" keyboard? (No extra code needed for any of the 10000s of "number textfields" used out there.)

Donna
+3  A: 

Here is an example for the solution suggested in the accepted answer. This doesn't handle other currencies or anything - in my case I only needed support for dollars, no matter what the locale/currency so this was OK for me:

-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
    replacementString:(NSString *)string {

    double currentValue = [textField.text doubleValue];
    double cents = round(currentValue * 100.0f);

    if ([string length]) {
        for (size_t i = 0; i < [string length]; i++) {
            unichar c = [string characterAtIndex:i];
            if (isnumber(c)) {
                cents *= 10;
                cents += c - '0'; 
            }            
        }
    } else {
        // back Space
        cents = floor(cents / 10);
    }

    textField.text = [NSString stringWithFormat:@"%.2f", cents / 100.0f];
    return NO;
}

The rounds and floors are important a) because of the floating-point representation sometimes losing .00001 or whatever and b) the format string rounding up any precision we deleted in the backspace part.

Mike Weller
this piece of code doesn't seem to work after entering the 15th number. I entered 999999999999999 and then on the next 9, the output was 100000000000000.00
Joo Park
You are losing precision because it uses floating point numbers.
Mike Weller
For most use cases this is a great solution +1
Cirrostratus
I have slightly modified your algorithm so that it deposits fractions of a penny into my offshore account. It is working already. MUA HA HA.
ZaBlanc
+1  A: 

I built a custom Number pad view controller with decimal point... check it out:

http://sites.google.com/site/psychopupnet/iphone-sdk/tenkeypadcustom10-keypadwithdecimal

Richard