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 an
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:
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.
Here's how to do it without using floats, round() or ceil() in a currency agnostic manner.
In you view controller, set up the following instance variables (with associated @property statements if that's your bag):
@interface MyViewController : UIViewController <UITextFieldDelegate> {
@private
UITextField *firstResponder;
NSNumberFormatter *formatter;
NSInteger currencyScale;
NSString *enteredDigits;
}
@property (nonatomic, readwrite, assign) UITextField *firstResponder;
@property (nonatomic, readwrite, retain) NSNumberFormatter *formatter;
@property (nonatomic, readwrite, retain) NSString *enteredDigits;
@end
and your viewDidLoad method should contain the following:
- (void)viewDidLoad {
[super viewDidLoad];
NSNumberFormatter *aFormatter = [[NSNumberFormatter alloc] init];
[aFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
currencyScale = -1 * [aFormatter maximumFractionDigits];
self.formatter = aFormatter;
[aFormatter release];
}
Then implement your UITextFieldDelegate methods as follows:
#pragma mark -
#pragma mark UITextFieldDelegate methods
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// Keep a pointer to the field, so we can resign it from a toolbar
self.firstResponder = textField;
self.enteredDigits = @"";
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if ([self.enteredDigits length] > 0) {
// Get the amount
NSDecimalNumber *result = [[NSDecimalNumber decimalNumberWithString:self.enteredDigits] decimalNumberByMultiplyingByPowerOf10:currencyScale];
NSLog(@"result: %@", result);
}
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
// Check the length of the string
if ([string length]) {
self.enteredDigits = [self.enteredDigits stringByAppendingFormat:@"%d", [string integerValue]];
} else {
// This is a backspace
NSUInteger len = [self.enteredDigits length];
if (len > 1) {
self.enteredDigits = [self.enteredDigits substringWithRange:NSMakeRange(0, len - 1)];
} else {
self.enteredDigits = @"";
}
}
NSDecimalNumber *decimal = nil;
if ( ![self.enteredDigits isEqualToString:@""]) {
decimal = [[NSDecimalNumber decimalNumberWithString:self.enteredDigits] decimalNumberByMultiplyingByPowerOf10:currencyScale];
} else {
decimal = [NSDecimalNumber zero];
}
// Replace the text with the localized decimal number
textField.text = [self.formatter stringFromNumber:decimal];
return NO;
}
Only tested this with pounds and pence, but it should work with Japanese Yen too. If you want to format decimals for non-currency purposes, then just read the documentation on NSNumberFormatter and set whatever format/maximumFractionDigits you want.
As of iOS4.1, there is a new keyboard type UIKeyboardTypeNumberPad, but unfortunately, as of yet it doesn't seem to appear in the Interface Builder pick list.
I think it would be good to point out that as of iOS 4.1 you can use the new UIKeyboardTypeDecimalPad
.
So now you just have to:
myTextField.keyboardType=UIKeyboardTypeDecimalPad;
A Swift 2 implementation of Mike Weller's post, also only USD:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let str = textField.text else {
textField.text = "0.00"
return false
}
let value = (str as NSString).doubleValue
var cents = round(value * 100)
if string.characters.count > 0 {
for c in string.characters {
if let num = Int(String(c)) {
cents *= 10
cents += Double(num)
}
}
}
else {
cents = floor(cents / 10)
}
textField.text = NSString(format: "%.2f", cents/100) as String
return false
}
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.