comma separated number with 2 digit fraction - swift - swift

I have a textfield that it's input is price, so I want to get both like this: 1,111,999.99. I wrote to make it possible but there are two problems. First, after four digits and 2 fraction digit (like 1,234.00) it resets to zero. Second, I can't put fraction in it (fraction is always .00)
how can i make a textfield that receives 1,111,999.99 as input?
in my custom UITextfield:
private var numberFormatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 0
formatter.numberStyle = .decimal
formatter.decimalSeparator = "."
formatter.groupingSeparator = ","
return formatter
}
var commaValue: String {
return numberFormatter.string(from: value)!
}
var value: NSNumber {
let number = numberFormatter.number(from: self.text ?? "0")
return number!
}
and in my textfieldDidChange method:
#IBAction func textfieldEditingChanged(_ sender: Any) {
let textfield = sender as! UITextField
textfield.text = textfield.commaValue
}

Solved it temporarily this way:
var formattedNumber: String {
guard self.text != "" else {return ""}
var fraction = ""
var digit = ""
let fractionExists = self.text!.contains(".")
let num = self.text?.replacingOccurrences(of: ",", with: "")
let sections = num!.characters.split(separator: ".")
if sections.first != nil
{
let str = String(sections.first!)
let double = Double(str)
guard double != nil else {return self.text ?? ""}
digit = numberFormatter.string(from: NSNumber(value: double ?? 0))!
}
if sections.count > 1
{
fraction = String(sections[1])
if fraction.characters.count > 2
{
fraction = String(fraction.prefix(2))
}
return "\(digit).\(fraction)"
}
if fractionExists
{
return "\(digit)."
}
return digit
}
.
#IBAction func textfieldEditingChanged(_ sender: Any) {
let textfield = sender as! UITextField
textfield.text = textfield.formattedNumber
}

Related

How to format numbers in a textField with math equation string?

I'm trying to format numbers in a UITextField consists of math equation string: "number + number".
At the moment I can type just a single number, then convert it to Double -> format with NSNumberFormatter -> convert back to String -> assign to textField.text:
The code:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.locale = .current
formatter.roundingMode = .down
let numberString = textField.text ?? ""
guard let range = Range(range, in: numberString) else { return false }
let updatedString = numberString.replacingCharacters(in: range, with: string)
let correctDecimalString = updatedString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "")
guard let value = Double(completeString) else { return false }
let formattedNumber = formatter.string(for: value)
textField.text = formattedNumber
return string == formatter.decimalSeparator
}
Now I want to add a calculation functionality and display a simple math equation in a textField as "number + number", but each number should be formatted as shown above. Example (but without formatting):
I can't properly implement that. The logic for me was: track the String each time new char inserts -> if it has math sign extract numbers -> convert them to Double -> format with NSNumberFormatter -> convert back to String -> construct a new String "number + number".
The code I tried:
if let firstString = completeString.split(separator: "+").first, let secondString = completeString.split(separator: "+").last {
guard let firstValue = Double(firstString) else { return false }
guard let secondValue = Double(secondString) else { return false }
let firstFormattedNumber = formatter.string(for: firstValue)
let secondFormattedNumber = formatter.string(for: secondValue)
textField.text = "\(firstFormattedNumber ?? "") + \(secondFormattedNumber ?? "")"
// another try
if completeString.contains("+") {
let stringArray = completeString.components(separatedBy: "+")
for character in stringArray {
print(character)
guard let value = Double(character) else { return false }
guard let formattedNumber = formatter.string(for: value) else { return false }
textField.text = "\(formattedNumber) + "
}
}
But it's not working properly. I tried to search but didn't find any similar questions.
Test project on GitHub
How can I format the numbers from such a string?
Here is how I was able to solve my question:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.locale = .current
formatter.roundingMode = .down
//set of possible math operations
let symbolsSet = Set(["+","-","x","/"])
let numberString = textField.text ?? ""
guard let range = Range(range, in: numberString) else { return false }
let updatedString = numberString.replacingCharacters(in: range, with: string)
let correctDecimalString = updatedString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "")
//receive math symbol user typed
let symbol = symbolsSet.filter(completeString.contains).first ?? ""
//receive number of symbols in a String. If user wants to type more than one math symbol - do not insert
let amountOfSymbols = completeString.filter({String($0) == symbol}).count
if amountOfSymbols > 1 { return false }
//receive numbers typed by user
let numbersArray = completeString.components(separatedBy: symbol)
//check for each number - if user wants to type more than one decimal sign - do not insert
for number in numbersArray {
let amountOfDecimalSigns = number.filter({$0 == "."}).count
if amountOfDecimalSigns > 1 { return false }
}
guard let firstNumber = Double(String(numbersArray.first ?? "0")) else { return true }
guard let secondNumber = Double(String(numbersArray.last ?? "0")) else { return true }
let firstFormattedNumber = formatter.string(for: firstNumber) ?? ""
let secondFormattedNumber = formatter.string(for: secondNumber) ?? ""
// if user typed math symbol - show 2 numbers and math symbol, if not - show just first typed number
textField.text = completeString.contains(symbol) ? "\(firstFormattedNumber)\(symbol)\(secondFormattedNumber)" : "\(firstFormattedNumber)"
return string == formatter.decimalSeparator
}

Swift Calculator - Cannot enter Zero after decimal

I am exploring a Swift Calculator Code and everything works fine except that I am not able to input a Zero after the decimal sign (for ex.: 12,001 or 1,301) and I cannot find the solution. I tried already a few things and unfortunately I cannot find a question solving this issue.
Thanks a lot for your help!
Here are the main parts of the code.
private var total: Double = 0
private var temp: Double = 0
private var operating = false
private var decimal = false
private var operation: OperationType = .none
private let kDecimalSeparator = Locale.current.decimalSeparator!
private let kMaxLength = 11
private let kTotal = "total"
private enum OperationType {
case none, addition, substraction, multiplication, division, percent
}
// Format
private let auxFormatter: NumberFormatter = {
let formatter = NumberFormatter()
let locale = Locale.current
formatter.decimalSeparator = locale.decimalSeparator
formatter.numberStyle = .decimal
formatter.maximumIntegerDigits = 100
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 100
return formatter
}()
// Format result
private let auxTotalFormatter: NumberFormatter = {
let formatter = NumberFormatter()
let locale = Locale.current
formatter.decimalSeparator = locale.decimalSeparator
formatter.numberStyle = .decimal
formatter.maximumIntegerDigits = 100
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 100
return formatter
}()
// Default screen format
private let printFormatter: NumberFormatter = {
let formatter = NumberFormatter()
let locale = Locale.current
formatter.decimalSeparator = locale.decimalSeparator
formatter.numberStyle = .decimal
formatter.maximumIntegerDigits = 9
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 8
return formatter
}()
#IBAction func numberDecimalAction(_ sender: UIButton) {
let currentTemp = auxTotalFormatter.string(from: NSNumber(value: temp))!
if resultLabel.text?.contains(kDecimalSeparator) ?? false || (!operating && currentTemp.count >= kMaxLength) {
return
}
resultLabel.text = resultLabel.text! + kDecimalSeparator
decimal = true
selectVisualOperation()
sender.shine()
}
#IBAction func numberAction(_ sender: UIButton) {
operatorAC.setTitle("C", for: .normal)
var currentTemp = auxTotalFormatter.string(from: NSNumber(value: temp))!
if !operating && currentTemp.count >= kMaxLength {
return
}
currentTemp = auxFormatter.string(from: NSNumber(value: temp))!
// After selecting an operation
if operating {
total = total == 0 ? temp : total
resultLabel.text = ""
currentTemp = ""
operating = false
}
// After selecting decimal
if decimal {
currentTemp = "\(currentTemp)\(kDecimalSeparator)"
decimal = false
}
if resultLabel.text?.contains(kDecimalSeparator) ?? true {
let number = String(sender.tag-1)
let currentTemp1 = currentTemp.replacingOccurrences(of: ".", with: "", options: .literal, range: nil)
let currentTemp2 = currentTemp1.replacingOccurrences(of: ",", with: ".", options: .literal, range: nil)
temp = Double(currentTemp2 + String(number))!
resultLabel.text = printFormatter.string(from: NSNumber(value: temp))
selectVisualOperation()
sender.shine()
}
else {
let number = String(sender.tag-1)
temp = Double(currentTemp.replacingOccurrences(of: ".", with: "", options: .literal, range: nil) + String(number))!
resultLabel.text = printFormatter.string(from: NSNumber(value: temp))
selectVisualOperation()
sender.shine()
}
}
// Clear
private func clear() {
if operation == .none {
total = 0
}
operation = .none
operatorAC.setTitle("AC", for: .normal)
if temp != 0 {
temp = 0
resultLabel.text = "0"
} else {
total = 0
result()
}
}
`
Let’s assume that you’re doing the traditional calculator style input with buttons for the digits, a decimal separator button and a clear button. The problem with your algorithm is that NumberFormatter with a minimumFractionalDigits of zero will drop trailing digits. So if you try to enter “1.000”, it will say “ok the value is 1, so the string representation of that with zero fraction digits is ‘1’”. The effect of this is that trailing zeros will never appear in the resulting string.
One approach is to adjust minimumFractionalDigits based upon how many fractional digits have been entered thus far. To facilitate that, you need to be able to keep track of how fractional digits have been entered.
There are a number of ways of doing that. One way is through “state management” (e.g. have properties to keep track of whether decimal has been entered already and how many fractional digits have been entered thus far, etc.). To keep it simple, I’m just going to calculate this from the raw user string input:
class ViewController: UIViewController {
let decimalSeparator = Locale.current.decimalSeparator ?? "."
/// The number formatter
///
/// Note, we don't need to set the decimal separator, as it defaults to the current separator.
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
/// The label containing the formatted number
#IBOutlet weak var resultLabel: UILabel!
/// This is the user's raw input, just digits and 0 or one decimal separator, not the formatted number in the label
private var input: String = ""
/// Just add keystroke to `input` string and then format the label.
#IBAction func didTapDigit(_ button: UIButton) {
let digit = ... // determine the numeric value associated with the button that the user tapped; personally I wouldn’t use the `tag` number, but I don’t want to drag us down a tangent, so just do this however you want
addCharacterToInput("\(digit)")
updateLabel()
}
#IBAction func didTapClear(_ sender: UIButton) {
resetInput()
resultLabel.text = "0"
}
/// Only add decimal separator if there's not one there already.
#IBAction func didTapDecimal(_ sender: UIButton) {
if !hasDecimalSeparator() {
addCharacterToInput(".")
}
updateLabel()
}
}
private extension ViewController {
func addCharacterToInput(_ string: String) {
input += String(string)
}
func resetInput() {
input = ""
}
/// How many decimal places in user input.
///
/// - Returns: Returns `nil` if no decimal separator has been entered yet. Otherwise it returns the number of characters after the decimal separator.
func decimalPlaces() -> Int? {
guard let range = input.range(of: decimalSeparator) else {
return nil
}
return input.distance(from: range.upperBound, to: input.endIndex)
}
/// Does the user input include a decimal separator?
/// - Returns: Returns `true` if decimal separator present. Returns `false` if not.
func hasDecimalSeparator() -> Bool {
input.contains(decimalSeparator)
}
/// Update the label on the basis of the `input` string of the raw user input.
func updateLabel() {
let fractionalDigits = decimalPlaces() // figure out decimal places from `input` string
formatter.minimumFractionDigits = fractionalDigits ?? 0 // set formatter accordingly
guard
let value = Double(input), // safely get value from user input ...
var string = formatter.string(for: value) // ...and build base string from that.
else {
resultLabel.text = "Error"
return
}
if fractionalDigits == 0 { // Note, if not `nil` and is zero, that means the user hit decimal separator but has entered no digits yet; we need to manually add decimal separator in output in this scenario
string += decimalSeparator
}
resultLabel.text = string
}
}
That yields:

Converting text field data to decimal value and back to string

I converted text field string into a double to do calculations and then back to a string to output it on a label. I am now working with currency inputs so I need to convert it to a decimal rather than a double. Can someone help?
func calcTotal() {
let totalConv: Double? = Double(totalTextField.text!)
let tipConv: Double? = Double(tipTextField.text!)
guard totalConv != nil && tipConv != nil else {
return
}
let result = totalConv! * ((tipConv! / 100) + 1)
let output = String(format: "$ %.2f", result)
totalAmount.text = String(output)
}
You will just need to use Decimal(string:) initializer and NumberFormatter (currency style) to format your decimal value.
func calcTotal() {
guard
let totalConv = Decimal(string: totalTextField.text!),
let tipConv = Decimal(string: tipTextField.text!)
else { return }
let result = totalConv * ((tipConv / 100) + 1)
totalAmount.text = Formatter.currency.string(for: result)
}
extension Formatter {
static let currency: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
return numberFormatter
}()
}

Formatting Numbers in Swift 3

I want to format number to this: 123.234.234.234 from 123234234234 depends on what the user types into the text field.
I don't want to manage currency, it's not about currency, it is about the user has to type in a number and this number should be formatted correctly to be easier to read.
Not with a comma, with a dot.
I found only currency stuff in the whole research
What you are looking for is probably groupingSeparator of NumberFormatter
let formater = NumberFormatter()
formater.groupingSeparator = "."
formater.numberStyle = .decimal
let formattedNumber = formater.string(from: number)
There's actually much easier solution (there is no need to create NumberFormatter instance) and it takes into account the user's language:
let result = String(format: "%ld %#", locale: Locale.current, viewCount, "views")
Result for value 1000000 with English:
1,000,000
Russian:
1 000 000
p.s. in Android it's exactly the same String.format(Locale.getDefault(), "%,d %s", viewCount, "views")
You can do it with NumberFormatter:
let yourNumber = 123234234234
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = NumberFormatter.Style.decimal
numberFormatter.groupingSeparator = "."
let formattedNumber = numberFormatter.string(from: NSNumber(value:yourNumber))
Details
Xcode 9.2, Swift 4
Xcode 10.2 (10E125), Swift 5
Solution
import Foundation
extension String { var toLocale: Locale { return Locale(identifier: self) } }
extension NumberFormatter {
convenience init(numberStyle: NumberFormatter.Style, groupingSeparator: String?, decimalSeparator: String?) {
self.init()
set(numberStyle: numberStyle, groupingSeparator: groupingSeparator, decimalSeparator: decimalSeparator)
}
convenience init(numberStyle: NumberFormatter.Style, locale: Locale) {
self.init()
set(numberStyle: numberStyle, locale: locale)
}
func set(numberStyle: NumberFormatter.Style, groupingSeparator: String?, decimalSeparator: String?) {
self.locale = nil
self.numberStyle = numberStyle
self.groupingSeparator = groupingSeparator
self.decimalSeparator = decimalSeparator
}
func set(numberStyle: NumberFormatter.Style, locale: Locale?) {
self.numberStyle = numberStyle
self.locale = locale
}
}
extension Numeric {
func format(formatter: NumberFormatter) -> String? {
if let num = self as? NSNumber { return formatter.string(from: num) }
return nil
}
}
Usage
let formatter = NumberFormatter(numberStyle: .decimal, locale: "fr_FR".toLocale)
print(value.format(formatter: formatter))
formatter.set(numberStyle: .decimal, groupingSeparator: " ", decimalSeparator: ".")
print(value.format(formatter: formatter))
Full sample
Do not forget to add the solution code here
func test<T: Numeric>(value: T) {
print("=========================================================")
print("\(T.self), value = \(value)")
let formatter = NumberFormatter(numberStyle: .decimal, locale: "fr_FR".toLocale)
print(value.format(formatter: formatter) ?? "nil")
formatter.set(numberStyle: .currency, locale: "de_DE".toLocale)
print(value.format(formatter: formatter) ?? "nil")
formatter.set(numberStyle: .decimal, groupingSeparator: " ", decimalSeparator: ".")
print(value.format(formatter: formatter) ?? "nil")
}
func print(title: String, value: String?) {
if let value = value { print("\(title) \(value)") }
}
test(value: Int(10000))
test(value: Double(10000.231))
test(value: Float(10000.231))
Result
=========================================================
Int, value = 10000
10 000
10.000,00 €
10 000
=========================================================
Double, value = 10000.231
10 000,231
10.000,23 €
10 000.231
=========================================================
Float, value = 10000.231
10 000,231
10.000,23 €
10 000.231
swift 4
extension Int {
func formatnumber() -> String {
let formater = NumberFormatter()
formater.groupingSeparator = "."
formater.numberStyle = .decimal
return formater.string(from: NSNumber(value: self))!
}
}
For leading zeros (Swift 5.2)
String(format: "%02d", intNumber) // 6 -> "06"
String(format: "%03d", intNumber) // 66 -> "066"
String(format: "%04d", intNumber) // 666 -> "0666"
from: https://stackoverflow.com/a/25566860/1064316
For swift 4, I implemented an extension where I can choose the formatting or use default one if none is selected.
extension Int {
func formatnumber(groupingSeparator: String?) -> String {
let formater = NumberFormatter()
formater.groupingSeparator = (groupingSeparator != nil) ? groupingSeparator! : ","
formater.numberStyle = .decimal
return formater.string(from: NSNumber(value: self))!
}
}
My script as an example:
1) Add extension to project
extension String {
public func subString(startIndex: String.Index, endIndex: String.Index) -> String {
return String(self[startIndex...endIndex])
}
public func subString(_ from: Int, _ to: Int) -> String {
let startIndex = self.index(self.startIndex, offsetBy: from)
let endIndex = self.index(self.startIndex, offsetBy: to)
return String(self[startIndex...endIndex])
}
}
2) Create file Utilites.swift and add my method
public func priceNumFormat(_ number: String)->String{
var formattedNumber = number
var print = number
var prefix = ""
if number.range(of:"-") != nil {
let index = number.index(of:"-")
formattedNumber.remove(at: index ?? formattedNumber.endIndex)
prefix = "-"
}
if formattedNumber.range(of:".") != nil {
let index = formattedNumber.index(of:".")
formattedNumber = formattedNumber.subString(startIndex: formattedNumber.startIndex, endIndex: index ?? formattedNumber.endIndex)
formattedNumber.remove(at: index ?? formattedNumber.endIndex)
}
if formattedNumber.count == 8 //10 000 000
{
let num0 = formattedNumber.subString(0, 1)
let num1 = formattedNumber.subString(2, 4)
let num2 = formattedNumber.subString(5, 7)
print = "\(num0) \(num1) \(num2)"
}
if formattedNumber.count == 7 //1 000 000
{
let num0 = formattedNumber.subString(0, 0)
let num1 = formattedNumber.subString(1, 3)
let num2 = formattedNumber.subString(4, 6)
print = "\(num0) \(num1) \(num2)"
}
if formattedNumber.count == 6 //100 000
{
let num0 = formattedNumber.subString(0, 2)
let num1 = formattedNumber.subString(3, 5)
print = "\(num0) \(num1)"
}
if formattedNumber.count == 5 //10 000
{
let num0 = formattedNumber.subString(0, 1)
let num1 = formattedNumber.subString(2, 4)
print = "\(num0) \(num1)"
}
if formattedNumber.count == 4 //1 000
{
let num0 = formattedNumber.subString(0, 0)
let num1 = formattedNumber.subString(1, 3)
print = "\(num0) \(num1)"
}
if formattedNumber.count == 3 //100
{
print = formattedNumber
}
if prefix.count > 0
{
print = "- \(print)"
}
return print;
}
3) Add code in your UIController
let utils = Utilites()
private func test(){
var price = self.utils.priceNumFormat("-12345678.000")
print("\(price)") //-12 345 678
price = self.utils.priceNumFormat("-1234567.000")
print("\(price)") //-1 234 567
price = self.utils.priceNumFormat("-123456.000")
print("\(price)") //-123 456
price = self.utils.priceNumFormat("-12345.000")
print("\(price)") //-12 345
price = self.utils.priceNumFormat("-1234.000")
print("\(price)") //-1 234
price = self.utils.priceNumFormat("-123.000")
print("\(price)") //-123
}

How to format currency in text field [duplicate]

I have a number let’s say 0.00.
When the user taps 1. We should have 0.01
When the user taps 2. We should display 0.12
When the user taps 3. We should display 1.23
When the user taps 4. We should display 12.34
How can I do that with Swift?
For Swift 3. Input currency format on a text field (from right to left)
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
}
#objc func myTextFieldDidChange(_ textField: UITextField) {
if let amountString = textField.text?.currencyInputFormatting() {
textField.text = amountString
}
}
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}
You can create a currency text field subclassing UITextField. Add a target for UIControlEvents .editingChanged. Add a selector method to filter the digits from your textfield string. After filtering all non digits from your string you can format again your number using NumberFormatter as follow:
Xcode 11.5 • Swift 5.2 or later
import UIKit
class CurrencyField: UITextField {
var decimal: Decimal { string.decimal / pow(10, Formatter.currency.maximumFractionDigits) }
var maximum: Decimal = 999_999_999.99
private var lastValue: String?
var locale: Locale = .current {
didSet {
Formatter.currency.locale = locale
sendActions(for: .editingChanged)
}
}
override func willMove(toSuperview newSuperview: UIView?) {
// you can make it a fixed locale currency if needed
// self.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
Formatter.currency.locale = locale
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
keyboardType = .numberPad
textAlignment = .right
sendActions(for: .editingChanged)
}
override func deleteBackward() {
text = string.digits.dropLast().string
// manually send the editingChanged event
sendActions(for: .editingChanged)
}
#objc func editingChanged() {
guard decimal <= maximum else {
text = lastValue
return
}
text = decimal.currency
lastValue = text
}
}
extension CurrencyField {
var doubleValue: Double { (decimal as NSDecimalNumber).doubleValue }
}
extension UITextField {
var string: String { text ?? "" }
}
extension NumberFormatter {
convenience init(numberStyle: Style) {
self.init()
self.numberStyle = numberStyle
}
}
private extension Formatter {
static let currency: NumberFormatter = .init(numberStyle: .currency)
}
extension StringProtocol where Self: RangeReplaceableCollection {
var digits: Self { filter (\.isWholeNumber) }
}
extension String {
var decimal: Decimal { Decimal(string: digits) ?? 0 }
}
extension Decimal {
var currency: String { Formatter.currency.string(for: self) ?? "" }
}
extension LosslessStringConvertible {
var string: String { .init(self) }
}
View Controller
class ViewController: UIViewController {
#IBOutlet weak var currencyField: CurrencyField!
override func viewDidLoad() {
super.viewDidLoad()
currencyField.addTarget(self, action: #selector(currencyFieldChanged), for: .editingChanged)
currencyField.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
}
#objc func currencyFieldChanged() {
print("currencyField:",currencyField.text!)
print("decimal:", currencyField.decimal)
print("doubleValue:",(currencyField.decimal as NSDecimalNumber).doubleValue, terminator: "\n\n")
}
}
Sample project
SwiftUI version of this post here
I started with Leo Dabus' answer (which didn't work out of the box for me) and in the process of trying to simplify and make it work ended up with this, which I think is pretty lean & clean if I do say so myself 😎
class CurrencyTextField: UITextField {
/// The numbers that have been entered in the text field
private var enteredNumbers = ""
private var didBackspace = false
var locale: Locale = .current
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
override func deleteBackward() {
enteredNumbers = String(enteredNumbers.dropLast())
text = enteredNumbers.asCurrency(locale: locale)
// Call super so that the .editingChanged event gets fired, but we need to handle it differently, so we set the `didBackspace` flag first
didBackspace = true
super.deleteBackward()
}
#objc func editingChanged() {
defer {
didBackspace = false
text = enteredNumbers.asCurrency(locale: locale)
}
guard didBackspace == false else { return }
if let lastEnteredCharacter = text?.last, lastEnteredCharacter.isNumber {
enteredNumbers.append(lastEnteredCharacter)
}
}
}
private extension Formatter {
static let currency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
return formatter
}()
}
private extension String {
func asCurrency(locale: Locale) -> String? {
Formatter.currency.locale = locale
if self.isEmpty {
return Formatter.currency.string(from: NSNumber(value: 0))
} else {
return Formatter.currency.string(from: NSNumber(value: (Double(self) ?? 0) / 100))
}
}
}
Try this piece of code:
struct DotNum {
private var fraction:String = ""
private var intval:String = ""
init() {}
mutating func enter(s:String) {
if count(fraction) < 2 {
fraction = s + fraction
} else {
intval = s + intval
}
}
private var sFract:String {
if count(fraction) == 0 { return "00" }
if count(fraction) == 1 { return "0\(fraction)" }
return fraction
}
var stringVal:String {
if intval == "" { return "0.\(sFract)" }
return "\(intval).\(sFract)"
}
}
var val = DotNum()
val.enter("1")
val.stringVal
val.enter("2")
val.stringVal
val.enter("3")
val.stringVal
val.enter("4")
val.stringVal
My final code thanks for your help
extension Double {
var twoDigits: Double {
let nf = NSNumberFormatter()
nf.numberStyle = NSNumberFormatterStyle.DecimalStyle
nf.minimumFractionDigits = 2
nf.maximumFractionDigits = 2
return self
}
}
var cleanText:String!
let number:String = sender.currentTitle as String!
if(amountDisplay.text != nil)
{
cleanText = String(Array(amountDisplay.text!).map{String($0)}.filter{ $0.toInt() != nil }.map{Character($0)} ) as String
cleanText = cleanText + number
}else{
cleanText = number
}
amount = (Double(cleanText.toInt()!) / 100).twoDigits
formatter.locale = NSLocale(localeIdentifier: currencies[current_currency_index])
amountDisplay.text = "\(formatter.stringFromNumber(amount!)!)"
Here is a code for swift 2
#IBOutlet weak var txtAmount: UITextField!
//MARK: - UITextField Delegate -
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
if string.characters.count == 0 {
return true
}
let userEnteredString = textField.text ?? ""
var newString = (userEnteredString as NSString).stringByReplacingCharactersInRange(range, withString: string) as NSString
newString = newString.stringByReplacingOccurrencesOfString(".", withString: "")
let centAmount : NSInteger = newString.integerValue
let amount = (Double(centAmount) / 100.0)
if newString.length < 16 {
let str = String(format: "%0.2f", arguments: [amount])
txtAmount.text = str
}
return false //return false for exact out put
}
Note : Connect delegate for textField from storyboard or programatically
Just for fun: copied Thomas's answer (full credits -and points- to him please) into a file to run as a Swift 4.1 script (with minor fixes):
dotnum.swift:
#!/usr/bin/swift
struct DotNum {
private var fraction:String = ""
private var intval:String = ""
init() {}
mutating func enter(_ s:String) {
if fraction.count < 2 {
fraction = s + fraction
} else {
intval = s + intval
}
}
private var sFract:String {
if fraction.count == 0 { return "00" }
if fraction.count == 1 { return "0\(fraction)" }
return fraction
}
var stringVal:String {
if intval == "" { return "0.\(sFract)" }
return "\(intval).\(sFract)"
}
}
var val = DotNum()
val.enter("1")
print(val.stringVal)
val.enter("2")
print(val.stringVal)
val.enter("3")
print(val.stringVal)
val.enter("4")
print(val.stringVal)
Then run it in a terminal:
$ chmod +x dotnum.swift
$ ./dotnum.swift
0.01
0.21
3.21
43.21
Thanks to everyone here. From all the answers here I managed to come out with mine.
First I set up the initial value of the textField to be:
private func commonInit() {
amountTextField.text = "0.00"
}
Then I use the UITextFieldDelegate to get the input value and the current textview.text:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//Need to check if the textfield.text can be evaluated as number or not before passing it to the function
//Get the current text value, and current user input and pass it to the
let formattedAmount = formatAmount(oldAmount: textField.text, userInput: string)
textField.text = formattedAmount
return false
}
Here go my private function to format the number to move from right to left:
private func formatAmount(currentText: String, userInput: String) -> String {
let amount = currentText.components(separatedBy: ".")
var intValue: String = amount[0]
var decimalValue: String = amount[1]
//backspace registered, need to move the number to the right
if userInput.isEmpty {
decimalValue.remove(at: decimalValue.index(before: decimalValue.endIndex))
decimalValue = intValue.last!.string + decimalValue
intValue.remove(at: intValue.index(before: intValue.endIndex))
if intValue.isEmpty {
intValue = "0"
}
} else {
//Need to consider if user paste value
if userInput.count > 2 {
decimalValue = String(userInput.suffix(2))
intValue = String(userInput.dropLast(2))
} else {
decimalValue = rmAmount[1] + userInput
//Add to int value (move to the right)
intValue = intValue + decimalValue.first!.string
if Int(intValue) == 0 {
intValue = "0" //00 -> 0
} else if intValue.first == "0" {
//remove 0 from at the first position in intValue
intValue.remove(at: intValue.startIndex) //01 -> 1
}
//Remove tenth place from decimal value since it goes to Int already
decimalValue.remove(at: decimalValue.startIndex)
}
}
return intValue + "." + decimalValue
}
This is basically it. Other extra implementations can be added by your own initiatives. Let me know if there is any problem with my implementation.
PS: This is of course only works for certain currency only, in my case, my apps is set up only for that local so thats why I use this way.
After a lot of trial and error with the suggested answers, I found a pretty straight forward solution:
The setup for the textField needs to be called in your view's setup.
In the switch statement, if the user puts in a number between 0 and 9, the number is added to the previous string value. The default case covers the backspace button and removes the last character from the string.
The locale for the numberFormatter is set to current, so it works with different currencies.
func setupTextField() {
textField.delegate = self
textField.tintColor = .clear
textField.keyboardType = .numberPad
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
setFormattedAmount(string)
return false
}
private func setFormattedAmount(_ string: String) {
switch string {
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
amountString = amountString + string
default:
if amountString.count > 0 {
amountString.removeLast()
}
}
let amount = (NSString(string: amountString).doubleValue) / 100
textField.text = formatAmount(amount)
}
private func formatAmount(_ amount: Double) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = .current
if let amount = formatter.string(from: NSNumber(value: amount)) {
return amount
}
return ""
}