11

I am trying to format currency input in a textfield in Swift as the user inputs it.

So far, I can only format it successfully when the user finishes inputting:

@IBAction func editingEnded(sender: AnyObject) {

    let formatter = NSNumberFormatter()
    formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
    formatter.locale = NSLocale(localeIdentifier: "en_US")
    var numberFromField = NSString(string: textField.text).doubleValue

    textField.text = formatter.stringFromNumber(numberFromField)
}

However, I would like for the currency to be formatted the moment the user inputs it. When I try to do it on the TextField actions "Editing Changed" or "Value Changed", I can only enter 1 number (if I enter 8, it becomes $8.00) but then once I enter a second number everything goes to $0.00 and I cannot enter further beyond that.

Any suggestions? I feel like this should be an easy fix but I can't quite get at it.

Gabriel Garrett
  • 2,087
  • 6
  • 27
  • 45

7 Answers7

22

I modified the function from earlier today. Works great for "en_US" and "fr_FR". However, for "ja_JP", the division by 100 I do to create decimals is a problem. You will need to have a switch or if/else statement that separates currencies with decimals and those that do not have them when formatted by the formatter. But I think this gets you in the space you wanted to be.

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var textField: UITextField!
    var currentString = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        self.textField.delegate = self
    }

    //Textfield delegates
    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { // return NO to not change text

        switch string {
        case "0","1","2","3","4","5","6","7","8","9":
            currentString += string
            println(currentString)
            formatCurrency(string: currentString)
        default:
            var array = Array(string)
            var currentStringArray = Array(currentString)
            if array.count == 0 && currentStringArray.count != 0 {
                currentStringArray.removeLast()
                currentString = ""
                for character in currentStringArray {
                    currentString += String(character)
                }
                formatCurrency(string: currentString)
            }
        }
        return false
    }

    func formatCurrency(#string: String) {
        println("format \(string)")
        let formatter = NSNumberFormatter()
        formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
        formatter.locale = NSLocale(localeIdentifier: "en_US")
        var numberFromField = (NSString(string: currentString).doubleValue)/100
        textField.text = formatter.stringFromNumber(numberFromField)
        println(textField.text )
    }
}
Steve Rosenberg
  • 19,348
  • 7
  • 46
  • 53
  • 1
    תודה לך! Awesome Steve, you've saved me again. Just a quick question, do you think there's a way to allow it so that when a user enters a number, it is a whole number until they enter the decimal? Like, say they enter 8, the output will be "$8.00" , and then a 9 : "$89.00", and until they enter a decimal, the numbers remain whole? – Gabriel Garrett Oct 26 '14 at 15:07
  • what happen when user press back button, i try after press back button, text will come again and start with where it's left, any idea to track backsapce button event – Ujesh Aug 21 '15 at 09:44
  • 2
    With Swift 2.0 this lines: var array = Array(string) var currentStringArray = Array(currentString) Needs to be like: var array = Array(string.characters) var currentStringArray = Array(currentString.characters) – Dinsen Nov 22 '15 at 00:30
  • You can make the division by 100 general by instead using.. / pow(10.0, Double(formatter.maximumFractionDigits)) – Mathieson Feb 28 '16 at 06:43
8

this works for me using NSNumberFormatter...

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

    // Construct the text that will be in the field if this change is accepted
    var oldText = textField.text as NSString
    var newText = oldText.stringByReplacingCharactersInRange(range, withString: string) as NSString!
    var newTextString = String(newText)

    let digits = NSCharacterSet.decimalDigitCharacterSet()
    var digitText = ""
    for c in newTextString.unicodeScalars {
        if digits.longCharacterIsMember(c.value) {
            digitText.append(c)
        }
    }

    let formatter = NSNumberFormatter()
    formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
    formatter.locale = NSLocale(localeIdentifier: "en_US")
    var numberFromField = (NSString(string: digitText).doubleValue)/100
    newText = formatter.stringFromNumber(numberFromField)

    textField.text = newText

    return false
}
Robert
  • 5,278
  • 43
  • 65
  • 115
  • Worked fine. The approved solution above had an issue with the backspace button for me. – Manos Nov 10 '15 at 21:28
  • This works great, however, not for currency which add a text behind the number. E.g. Swedish, Norwegian, Danish ect which will show `10 000,00 SEK`, you cannot remove anything. – Paul Peelen Jan 11 '16 at 13:50
5

Based on @Robert answer. Updated for Swift 2.0

//Textfield delegates
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { // return NO to not change text

    switch string {
    case "0","1","2","3","4","5","6","7","8","9":
        currentString += string
        formatCurrency(currentString)
    default:
        if string.characters.count == 0 && currentString.characters.count != 0 {
            currentString = String(currentString.characters.dropLast())
            formatCurrency(currentString)
        }
    }
    return false
}

func formatCurrency(string: String) {
    print("format \(string)")
    let formatter = NSNumberFormatter()
    formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
    formatter.locale = NSLocale(localeIdentifier: "en_US")
    let numberFromField = (NSString(string: currentString).doubleValue)/100
    self.amountField.text = formatter.stringFromNumber(numberFromField)
    print(self.amountField.text )
}
fahrulazmi
  • 734
  • 9
  • 12
3

For Swift 3.0

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

        // Construct the text that will be in the field if this change is accepted

        switch string {
        case "0","1","2","3","4","5","6","7","8","9":
            currentString += string
            formatCurrency(currentString)
        default:
            if string.characters.count == 0 && currentString.characters.count != 0 {
                currentString = String(currentString.characters.dropLast())
                formatCurrency(currentString)
            }
        }
        return false    }

    func formatCurrency(_ string: String) {
        print("format \(string)")
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = findLocaleByCurrencyCode("NGN")
        let numberFromField = (NSString(string: currentString).doubleValue)/100
        let temp = formatter.string(from: NSNumber(value: numberFromField))
        self.amountTextField.text = String(describing: temp!.characters.dropFirst())
    }

func findLocaleByCurrencyCode(_ currencyCode: String) -> Locale? {

    let locales = Locale.availableIdentifiers 
    var locale: Locale?     
    for   localeId in locales {     
      locale = Locale(identifier: localeId)     
      if let code = (locale! as NSLocale).object(forKey: NSLocale.Key.currencyCode) as? String { 
        if code == currencyCode {
                return locale       
        }   
    } 
}    
return locale }
Ajay Singh Mehra
  • 1,313
  • 9
  • 19
1

I worked out a normal currency format ( eg 1 is as $1.00, 88885 is as $8,8885.00 and 7555.8569 as $7,555.86.

@IBAction func lostpropertyclicked(sender: AnyObject) {
    var currentString = ""
    currentString = amountTF.text
    formatCurrency(string: currentString)
}

func formatCurrency(#string: String) {
    println("format \(string)")
    let formatter = NSNumberFormatter()
    formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
    formatter.locale = NSLocale(localeIdentifier: "en_US")
    var numberFromField = (NSString(string: currentString).doubleValue)
    currentString = formatter.stringFromNumber(numberFromField)!
    println(currentString )
}
piofusco
  • 229
  • 2
  • 14
Alvin George
  • 14,148
  • 92
  • 64
1

Swift 5

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // return NO to not change text

    switch string {
    case "0","1","2","3","4","5","6","7","8","9":
        currentString += string
        formatCurrency(string: currentString)
    default:
        if string.count == 0 && currentString.count != 0 {
            currentString = String(currentString.dropLast())
            formatCurrency(string: currentString)
        }
    }
    return false
}

func formatCurrency(string: String) {
    print("format \(string)")
    let formatter = NumberFormatter()
    formatter.numberStyle = NumberFormatter.Style.currency
    formatter.locale = NSLocale(localeIdentifier: "en_US") as Locale
    let numberFromField = (NSString(string: currentString).doubleValue)/100
    //replace billTextField with your text field name
    self.billTextField.text = formatter.string(from: NSNumber(value: numberFromField))
    print(self.billTextField.text ?? "" )
}
Myoung
  • 161
  • 5
0

This worked for me: Naming of the variables need to be improved though. Multiplying by 10 was easy but figuring out how to divide by 10 and round down was tricky with the pointers.

    let numberFormatter = NumberFormatter()
    numberFormatter.numberStyle = .currency


    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if textField == amountTextField {
        guard let text = textField.text else {return true}

        let oldDigits = numberFormatter.number(from: text) ?? 0
        var digits = oldDigits.decimalValue

        if let digit = Decimal(string: string) {
            let newDigits: Decimal = digit / 100

            digits *= 10
            digits += newDigits
        }
        if range.length == 1 {
            digits /= 10
            var result = Decimal(integerLiteral: 0)
            NSDecimalRound(&result, &digits, 2, Decimal.RoundingMode.down)
            digits = result
        }

        textField.text = NumberFormatter.localizedString(from: digits as NSDecimalNumber, number: .currency)
        return false
    } else {
        return true
    }
}