12

I have a UITextField in my application that is supposed to receive only numeric input from the user. This numeric input is supposed to represent currency (i.e. have only two decimal places), have the default value of 0.00 when nothing has been entered. I also would like to have a "$" sign that is on the far left, with the number right aligned, such that the number moves from right to left like such:

e.g. when entering the number "1234.56".

1. $ 0.00.

2. $ 0.01.

3. $ 0.12.

4. $ 1.23.

5. $ 12.34.

6. $ 123.45.

7. $ 1,234.56.

8. and so on...

I have the UITextField right aligned so that it already moves the numbers over to the left from the right. However, I need to place the "$" inside the UITextField, and I would like the default 0.00 value to be modified to the number entered by the user one digit at a time. The user should NOT have to enter the decimal point, as it should already be in the textfield holding its position. I then would like the number to have a "," to be automatically entered to separate every three digits. I would like the application to do this AS THE USER IS ENTERING THE NUMBERS, and not formatted afterwards when everything is entered. How would I do this? I have seen a lot of people say that I am to use NSNumberFormatter, but I want to know how do I use it in conjunction with the UITextField such that it formats the numeric text on the fly, as described above?

I have found the following question posted on StackOverflow some time ago:

What is the best way to enter numeric values with decimal points?

but I want to know how to actually incorporate it as a solution to my problem.

Community
  • 1
  • 1
syedfa
  • 2,801
  • 1
  • 41
  • 74

5 Answers5

15

Here's a nice way to do it. Remember to set your UITextField to self in the viewDidLoad method and your header file must conform to the UITextFieldDelegate protocol

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

    NSString *cleanCentString = [[textField.text componentsSeparatedByCharactersInSet: [[NSCharacterSet decimalDigitCharacterSet] invertedSet]] componentsJoinedByString:@""];
    NSInteger centValue = [cleanCentString intValue];
    NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
    NSNumber *myNumber = [f numberFromString:cleanCentString];
    NSNumber *result;

    if([textField.text length] < 16){
        if (string.length > 0)
        {
            centValue = centValue * 10 + [string intValue];
            double intermediate = [myNumber doubleValue] * 10 +  [[f numberFromString:string] doubleValue];
           result = [[NSNumber alloc] initWithDouble:intermediate];
        }
        else
        {
            centValue = centValue / 10;
            double intermediate = [myNumber doubleValue]/10;
            result = [[NSNumber alloc] initWithDouble:intermediate];
        }

        myNumber = result;
         NSLog(@"%ld ++++ %@", (long)centValue, myNumber);
            NSNumber *formatedValue;
            formatedValue = [[NSNumber alloc] initWithDouble:[myNumber doubleValue]/ 100.0f];
            NSNumberFormatter *_currencyFormatter = [[NSNumberFormatter alloc] init];
            [_currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
            textField.text = [_currencyFormatter stringFromNumber:formatedValue];
            return NO;
    }else{

        NSNumberFormatter *_currencyFormatter = [[NSNumberFormatter alloc] init];
        [_currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
        textField.text = [_currencyFormatter stringFromNumber:00];

        UIAlertView *alert = [[UIAlertView alloc]initWithTitle: @"Deposit Amount Limit"
                                                       message: @"You've exceeded the deposit amount limit. Kindly re-input amount"
                                                      delegate: self
                                             cancelButtonTitle:@"Cancel"
                                             otherButtonTitles:@"OK",nil];

        [alert show];
        return NO;
    }
    return YES;
}
Daniel
  • 598
  • 1
  • 6
  • 23
  • I think this should be marked as the right answer. It works like a charm. Thanks! – Claus Jan 26 '15 at 14:30
  • @RZO92 Thanks! Glad I could help out – Daniel Sep 08 '15 at 06:50
  • 1
    This approach falsely assumes edits are always made at the end of the string which ignores the possibility the user placed the cursor someplace else (this can be mitigated with a customer UITextField). Also, if performing financial calculations you should really be using NSDecimalNumber. – Brandon Mar 29 '16 at 04:38
  • 1
    To avoid rounding numbers under cents add this line `_currencyFormatter.roundingMode = NSNumberFormatterRoundFloor;` – orafaelreis Jan 19 '18 at 17:04
7

Updated Answer for SWIFT 3

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        let formatter = NumberFormatter()
        formatter.minimumFractionDigits = 2
        formatter.maximumFractionDigits = 2

        if string.characters.count > 0 {
            amountTypedString += string
            let decNumber = NSDecimalNumber(string: amountTypedString).multiplying(by: 0.01)
            let newString = "$" + formatter.string(from: decNumber)!
            textField.text = newString
        } else {
            amountTypedString = String(amountTypedString.characters.dropLast())
            if amountTypedString.characters.count > 0 {
                let decNumber = NSDecimalNumber(string: amountTypedString).multiplying(by: 0.01)
                let newString = "$" +  formatter.string(from: decNumber)!
                textField.text = newString
            } else {
                textField.text = "$0.00"
            }

        }
        return false

    }

    func textFieldShouldClear(_ textField: UITextField) -> Bool {
        amountTypedString = ""
        return true
    }
Priyatham51
  • 1,864
  • 1
  • 16
  • 24
3

Here is my solution in Swift. My approach was keeping track of the numbers entered and multiplying it by 0.01 and setting the result number with 2 decimal points using NSNumberFormatter. Note that I have the textfield keyboard set as the number pad.

amountTextfield?.keyboardType = UIKeyboardType.NumberPad

var amountTypedString = ""

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

    let formatter = NSNumberFormatter()
    formatter.minimumFractionDigits = 2
    formatter.maximumFractionDigits = 2

    if string.characters.count > 0 {
        amountTypedString += string
        let decNumber = NSDecimalNumber(float: Float(amountTypedString)!).decimalNumberByMultiplyingBy(0.01)
        let newString = "$" + formatter.stringFromNumber(decNumber)!
        textfield.text = newString
    } else {
        amountTypedString = String(amountTypedString.characters.dropLast())
        if amountTypedString.characters.count > 0 {
            let decNumber = NSDecimalNumber(float: Float(amountTypedString)!).decimalNumberByMultiplyingBy(0.01)
            let newString = "$" +  formatter.stringFromNumber(decNumber)!
            textfield.text = newString
        } else {
            textfield.text = "$0.00"
        }

    }
    return false

}

func textFieldShouldClear(textField: UITextField) -> Bool {
    amountTypedString = ""
    return true
}
julien
  • 437
  • 4
  • 9
  • In addition to providing the code, also include some text to identify important parts of the OP's question. – buczek Apr 14 '16 at 18:43
1

You likely need to check out the UITextFieldDelegate protocol. By assigning something (most commonly your ViewController class) as the UITextField's delegate, you'll be able to receive interactive text entry events from the control. In particular:

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

By returning NO from that method, you can prevent the text field from applying the keys pressed by the user.

However, even though you return NO there's nothing to prevent you from using the information passed to the delegate method to do your own manipulation of the text field contents in code.

In pseudo-code form:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if (string-contains-number) {
        alter textfield.text to be what it should be now, adding the number;
    }
    else {
        string contains something you don't wan the user typing, so ignore it.
    }

    // Tell iOS not to apply the user's change (since you just did by 
    // updating the text yourself:
    return NO;
}

Disclaimer: I haven't written actual code to test, so it's possible that altering the code inside this delegate method causes another shouldChangeCharactersInRange: message to fire. I don't believe that's the case, though. I don't believe the delegate event fires when you alter the text in code.

This obviously isn't as good as a custom control that already implements all the rules for a number text entry box (I'm having flashbacks to years of Visual Basic programming now...). But I don't know of any such control that someone's written and open-sourced.

Bill Patterson
  • 2,495
  • 1
  • 19
  • 20
  • Thanks very much for your reply, you've given me something to work with. Thanks again for your help! – syedfa Mar 05 '13 at 22:02
  • 2
    Don't forget to handle `if ([string length] == 0)`, indicating that the user pressed the delete key. – Olie Apr 08 '15 at 19:25
0

I have taken the answer from julien and modified it so other locales with differing currency formats are supported. I have also stored the number formatters for reuse so they are not created each time which is an expensive operation.

Overflow is handled by ignoring any input that would cause the stored value to overflow.

This solution assumes that text was entered only at the end of the text field, and as such will only operate on the end regardless of where the text cursor was prior to input.

let outputFormatter: NSNumberFormatter = {
    let formatter = NSNumberFormatter()
    formatter.numberStyle = .CurrencyStyle
    if formatter.maximumFractionDigits > 0 {
        formatter.multiplier = pow(10, -CGFloat(formatter.maximumFractionDigits))
    }
    return formatter
}()

let inputFormatter: NSNumberFormatter = {
    let formatter = NSNumberFormatter()
    formatter.numberStyle = .DecimalStyle
    return formatter
}()

private var value: UInt = 0

var currencyValue: NSDecimalNumber {
    let exponent = Int16(truncatingBitPattern: outputFormatter.maximumFractionDigits)
    return NSDecimalNumber(mantissa: UInt64(value), exponent: -exponent, isNegative: false)
}

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    if string.isEmpty {
        /* User pressed backspace, shift right one space. */
        value /= 10
    } else if let number = inputFormatter.numberFromString(string)?.unsignedIntegerValue {
        /* User entered a number, shift left one space and add. */
        let shiftedValue = UInt.multiplyWithOverflow(value, 10)
        let addedValue = UInt.addWithOverflow(shiftedValue.0, number)
        if !shiftedValue.overflow && !addedValue.overflow {
            value = addedValue.0
        }
    }

    textField.text = outputFormatter.stringFromNumber(NSDecimalNumber(unsignedInteger: value))
    return false
}

func textFieldShouldClear(textField: UITextField) -> Bool {
    value = 0
    return true
}

Using this in a playground with various locales gave me these results (sorry, can't embed directly yet).

Jake
  • 545
  • 2
  • 6