Formatting Numbers in Swift 3 - swift

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
}

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 String: append 0s after decimal separator

I need to append 0, 1, or 2 0s to a string, depends on its decimal separator, so that
"100", "100." and "100.0" becomes "100.00"
"100.8" becomes "100.80"
"100.85" remains unchanged
I could find the decimal separator and check its distance to end endIndex of the string, but is there an easier way of doing it?
NumberFormatter does this, but the actual string I have, isn't a plain number that can go through a formatter.
For example:
let amount = "123,456,789"
then formatted amount should be "123,456,789.00"
assumption:
the given string has at most one decimal separator with at most two decimal places
So there can't be string like: "123.4.4.5"
Also I want to use the decimal separator from NumberFormatter().decimalSeparator
You could pass the string through a decimal formatter to get the underlying number, and then back again through the formatter to get a formatted string:
let amount = "123,456,789"
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
let number = formatter.number(from: amount)
let newAmountString = formatter.string(from: number!) //"123,456,789.00"
(You should check that number is not nil before force unwrapping it, with if letor guard)
You could wrap this in a function:
func zeroPadding(toString: String) -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
guard let number = formatter.number(from: toString) else {
return nil
}
return formatter.string(from: number)
}
Here are some test cases:
zeroPadding(toString: "123,456,789") //"123,456,789.00"
zeroPadding(toString: "123,456,789.0") //"123,456,789.00"
zeroPadding(toString: "123,456,789.10") //"123,456,789.10"
zeroPadding(toString: "123,456,789.123") //"123,456,789.12"
zeroPadding(toString: "123.4567") //"123.46"
zeroPadding(toString: "Price: 1€ for a 💩") //nil
Or define it as an extension on String:
extension String {
func withZeroPadding() -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
guard let number = formatter.number(from: self) else {
return nil
}
return formatter.string(from: number)
}
}
And use it like this:
"123.4.4.5".withZeroPadding() //nil
"12.".withZeroPadding() //"12.00"
"123,456,789".withZeroPadding() //"123,456,789.00"
This is the following code snippet I have tested on Playground, it can be achieved more smartly but for now it is working.
//let amount = "123,456,789.545222323"
//let amount = "123,456,789."
let amount = "123,456,789"
let removeSpaces = amount.replacingOccurrences(of: " ", with: "")
if removeSpaces.count > 0
{
let arrSTR = removeSpaces.components(separatedBy: ".")
if arrSTR.count > 1
{
var strAfterDecimal = arrSTR[1]
if strAfterDecimal.count >= 2
{
strAfterDecimal = strAfterDecimal[0..<2]
}else if strAfterDecimal.count != 0
{
strAfterDecimal = "\(strAfterDecimal)0"
}else
{
strAfterDecimal = "00"
}
let finalSTR = String("\(arrSTR[0]).\(strAfterDecimal)")
print("Final with Decimal - \(finalSTR)")
}else
{
let finalSTR = String(arrSTR[0] + ".00")
print("Final without Decimal - \(finalSTR)")
}
}
extension String {
subscript(_ range: CountableRange<Int>) -> String {
let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound))
let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound))
return String(self[idx1..<idx2])
}
}

comma separated number with 2 digit fraction - 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
}

Check the language of numbers if it is English or Arabic/Persian

In my project I am planning to change the language of any type number to Persian and here is what I have done:
public extension String {
func perstianString (string: String)->String {
let digitSet = CharacterSet.decimalDigits
var finalString = String()
for ch in string.unicodeScalars {
if digitSet.contains(ch) {
let sh = convertoPersianNum(num: "\(ch)")
finalString += sh
}
} else {
finalString += "\(ch)"
}
}
return finalString
}
func convertoPersianNum(num: String)->String{
var retVlue = String()
var num1 = num
let number = NSNumber(value: Int(num1)!)
let numb = (num as NSString).intValue
let format = NumberFormatter()
format.locale = Locale(identifier: "fa_IR")
let faNumber = format.string(from: number)
return faNumber!
}
}
But when the source value has Persian numbers, the app crashes. Simply said, I want to check if it is a Persian number, don't do anything, else do the the conversion above:
let string = "ییسس ۱۲۳۴"
with this type do not do anything else do something.
public extension String {
private var isPersian: Bool {
let predicate = NSPredicate(format: "SELF MATCHES %#", "(?s).*\\p{Arabic}.*")
return predicate.evaluate(with: self)
}
private var toPersianNum: String {
let number = NSDecimalNumber(string: self)
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "fa_IR")
return formatter.string(from: number) ?? ""
}
var persian:String {
var retVal = String()
self.enumerateSubstrings(in: startIndex..<endIndex, options: .byComposedCharacterSequences) { (string, _, _, _) in
guard let string = string else {return}
if string.isPersian {
retVal += string
}else {
retVal += string.toPersianNum
}
}
return retVal
}
}
let string = "9001ییسس2345777 ۱۲۳۴2345"
string.persian // "۹۰۰۱ییسس۲۳۴۵۷۷۷ناعدد۱۲۳۴۲۳۴۵" (result)

Swift playground - How to convert a string with comma to a string with decimal

I'm new in the Swift world.
How can I converting a String with a comma to a String with a decimal?
The code work's fine with a dot (.)
The problem is when I'm using a comma (,) ... with: var price
The origin of the problem is the Decimal french keyboard use a comma (,) instead of a dot (.)
Don't know exactly how to use NSNumberFormatter or generatesDecimalNumbers if it's the key. There's probebly more than one options.
//The answer change if "2,25" or "2.25" is used.
var price : String = "2,25"
var priceFloat = (price as NSString).floatValue
//I need to have 2.25 as answer.
var costString = String(format:"%.2f", priceFloat)
Thank's for your time and your help!
update: Xcode 8.2.1 • Swift 3.0.2
You can use NumberFormatter() to convert your string to number. You just need to specify the decimalSeparator as follow:
extension String {
static let numberFormatter = NumberFormatter()
var doubleValue: Double {
String.numberFormatter.decimalSeparator = "."
if let result = String.numberFormatter.number(from: self) {
return result.doubleValue
} else {
String.numberFormatter.decimalSeparator = ","
if let result = String.numberFormatter.number(from: self) {
return result.doubleValue
}
}
return 0
}
}
"2.25".doubleValue // 2.25
"2,25".doubleValue // 2.25
let price = "2,25"
let costString = String(format:"%.2f", price.doubleValue) // "2.25"
You should do the currency formatting also with NumberFormat, so create a read-only computed property currency extending FloatingPoint protocol to return a formatted string from the String doubleValue property.
extension NumberFormatter {
convenience init(style: Style) {
self.init()
self.numberStyle = style
}
}
extension Formatter {
static let currency = NumberFormatter(style: .currency)
}
extension FloatingPoint {
var currency: String {
return Formatter.currency.string(for: self) ?? ""
}
}
let costString = "2,25".doubleValue.currency // "$2.25"
Formatter.currency.locale = Locale(identifier: "en_US")
"2222.25".doubleValue.currency // "$2,222.25"
"2222,25".doubleValue.currency // "$2,222.25"
Formatter.currency.locale = Locale(identifier: "pt_BR")
"2222.25".doubleValue.currency // "R$2.222,25"
"2222,25".doubleValue.currency // "R$2.222,25"
You can use for this Swift 3:
let currentAmount = "2,50"
currentAmount = currentAmount.replacingOccurrences(of: ",", with: ".")
print(currentAmount) // "2.50\n"
var price = "2,25"
price = price.replacingOccurrences(of: ",", with: ".")
var priceFloat = (price as NSString).floatValue
Nullable extension version:
extension String
{
static let customNumberFormatter = NumberFormatter()
var doubleValue: Double? {
String.customNumberFormatter.decimalSeparator = "."
if let result = String.customNumberFormatter.number(from: self) {
return result.doubleValue
} else {
String.customNumberFormatter.decimalSeparator = ","
if let result = String.customNumberFormatter.number(from: self) {
return result.doubleValue
}
}
return nil
}
}
EDIT: Updated to work with the current version of Swift:
let amount = "8,35"
var counter: Int = 0
var noCommaNumber: String!
for var carattere in (amount) {
if carattere == "," { carattere = "." }
if counter != 0 { noCommaNumber = "\(noCommaNumber ?? "\(carattere)")" + "\(carattere)" } else { noCommaNumber = "\(carattere)" } // otherwise first record will always be nil
counter += 1
}
let importo = Float(noCommaNumber)