Luchando con NSNumberFormatter en Swift por moneda

86

Estoy creando una aplicación de presupuesto que le permite al usuario ingresar su presupuesto y transacciones. Necesito permitir que el usuario ingrese peniques y libras desde campos de texto separados y deben formatearse junto con los símbolos de moneda. Tengo esto funcionando bien en este momento, pero me gustaría localizarlo, ya que actualmente solo funciona con GBP. He estado luchando para convertir ejemplos de NSNumberFormatter de Objective C a Swift.

Mi primer problema es el hecho de que necesito establecer los marcadores de posición para que los campos de entrada sean específicos de la ubicación de los usuarios. P.ej. Libras y peniques, dólares y centavos, etc.

El segundo problema es que los valores ingresados ​​en cada uno de los campos de texto, como 10216 y 32, deben formatearse y el símbolo de moneda específico para la ubicación del usuario debe agregarse. Entonces se convertiría en £ 10,216.32 o $ 10,216.32, etc.

Además, necesito usar el resultado del número formateado en un cálculo. Entonces, ¿cómo puedo hacer esto sin tener problemas sin tener problemas con el símbolo de moneda?

Cualquier ayuda será muy apreciada.

usuario3746428
fuente
2
¿Puedes publicar un ejemplo de código que no funciona?
NiñoScript

Respuestas:

205

Aquí hay un ejemplo sobre cómo usarlo en Swift 3. ( Editar : también funciona en Swift 4)

let price = 123.436 as NSNumber

let formatter = NumberFormatter()
formatter.numberStyle = .currency
// formatter.locale = NSLocale.currentLocale() // This is the default
// In Swift 4, this ^ has been renamed to simply NSLocale.current
formatter.string(from: price) // "$123.44"

formatter.locale = Locale(identifier: "es_CL")
formatter.string(from: price) // $123"

formatter.locale = Locale(identifier: "es_ES")
formatter.string(from: price) // "123,44 €"

Aquí está el viejo ejemplo sobre cómo usarlo en Swift 2.

let price = 123.436

let formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
// formatter.locale = NSLocale.currentLocale() // This is the default
formatter.stringFromNumber(price) // "$123.44"

formatter.locale = NSLocale(localeIdentifier: "es_CL")
formatter.stringFromNumber(price) // $123"

formatter.locale = NSLocale(localeIdentifier: "es_ES")
formatter.stringFromNumber(price) // "123,44 €"
NiñoScript
fuente
Gracias. He editado mi pregunta y he sido más específico.
user3746428
Según el ejemplo que proporcionó, logré implementar el formato de número en mi programa, por lo que ese bit está ordenado. Ahora solo necesito averiguar cómo configurar los marcadores de posición del campo de texto según la ubicación de los usuarios.
user3746428
2
No es necesario convertirlo en NSNumber, puede usar el método del formateador func string (para obj: Any?) -> String ?. Así que solo necesitas usar en string(for: price)lugar destring(from: price)
Leo Dabus
1
@LeoDabus, tienes razón, no sabía sobre ese método, aunque no estoy seguro de si debería editar mi respuesta, ya que creo que prefiero usar la API de NumberFormatter y ser explícito sobre el uso de NSNumber en lugar de dejarlo implícitamente echarlo dentro.
NiñoScript
Tenga en cuenta que el resultado de formatter.string (from :) es una cadena opcional, no una cadena (como lo implican los comentarios), por lo que deberá desenvolverse antes de su uso.
Ali Beadle
25

Swift 3:

Si buscas una solución que te brinde:

  • "5" = "$ 5"
  • "5.0" = "$ 5"
  • "5.00" = "$ 5"
  • "5.5" = "$ 5.50"
  • "5.50" = "$ 5.50"
  • "5.55" = "$ 5.55"
  • "5.234234" = "5.23"

Utilice lo siguiente:

func cleanDollars(_ value: String?) -> String {
    guard value != nil else { return "$0.00" }
    let doubleValue = Double(value!) ?? 0.0
    let formatter = NumberFormatter()
    formatter.currencyCode = "USD"
    formatter.currencySymbol = "$"
    formatter.minimumFractionDigits = (value!.contains(".00")) ? 0 : 2
    formatter.maximumFractionDigits = 2
    formatter.numberStyle = .currencyAccounting
    return formatter.string(from: NSNumber(value: doubleValue)) ?? "$\(doubleValue)"
}
Gregg
fuente
No es necesario inicializar un nuevo objeto NSNumber, puede usar el método del formateador en func string(for obj: Any?) -> String?lugar delstring(from:)
Leo Dabus
19

También he implementado la solución proporcionada por @ NiñoScript como una extensión:

Extensión

// Create a string with currency formatting based on the device locale
//
extension Float {
    var asLocaleCurrency:String {
        var formatter = NSNumberFormatter()
        formatter.numberStyle = .CurrencyStyle
        formatter.locale = NSLocale.currentLocale()
        return formatter.stringFromNumber(self)!
    }
}

Uso:

let amount = 100.07
let amountString = amount.asLocaleCurrency
print(amount.asLocaleCurrency())
// prints: "$100.07"

Swift 3

    extension Float {
    var asLocaleCurrency:String {
        var formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = Locale.current
        return formatter.string(from: self)!
    }
}
Michael Voccola
fuente
la extensión debe extenderse FloatingPoint para la versión Swift 3 y la cadena (de: el método es para NSNumber. Para los tipos de FlotingPoint, debe usar la cadena (para el método :). He publicado una extensión Swift 3
Leo Dabus
No use tipos flotantes para la moneda, use decimal.
adnako
17

Xcode 11 • Swift 5.1

extension Locale {
    static let br = Locale(identifier: "pt_BR")
    static let us = Locale(identifier: "en_US")
    static let uk = Locale(identifier: "en_GB") // ISO Locale
}

extension NumberFormatter {
    convenience init(style: Style, locale: Locale = .current) {
        self.init()
        self.locale = locale
        numberStyle = style
    }
}

extension Formatter {
    static let currency = NumberFormatter(style: .currency)
    static let currencyUS = NumberFormatter(style: .currency, locale: .us)
    static let currencyBR = NumberFormatter(style: .currency, locale: .br)
}

extension Numeric {
    var currency: String { Formatter.currency.string(for: self) ?? "" }
    var currencyUS: String { Formatter.currencyUS.string(for: self) ?? "" }
    var currencyBR: String { Formatter.currencyBR.string(for: self) ?? "" }
}

let price = 1.99

print(Formatter.currency.locale)  // "en_US (current)\n"
print(price.currency)             // "$1.99\n"

Formatter.currency.locale = .br
print(price.currency)  // "R$1,99\n"

Formatter.currency.locale = .uk
print(price.currency)  // "£1.99\n"

print(price.currencyBR)  // "R$1,99\n"
print(price.currencyUS)  // "$1.99\n"
Leo Dabus
fuente
3
No use tipos flotantes para la moneda, use decimal.
adnako
7

Detalles

  • Xcode 10.2.1 (10E1001), Swift 5

Solución

import Foundation

class CurrencyFormatter {
    static var outputFormatter = CurrencyFormatter.create()
    class func create(locale: Locale = Locale.current,
                      groupingSeparator: String? = nil,
                      decimalSeparator: String? = nil,
                      style: NumberFormatter.Style = NumberFormatter.Style.currency) -> NumberFormatter {
        let outputFormatter = NumberFormatter()
        outputFormatter.locale = locale
        outputFormatter.decimalSeparator = decimalSeparator ?? locale.decimalSeparator
        outputFormatter.groupingSeparator = groupingSeparator ?? locale.groupingSeparator
        outputFormatter.numberStyle = style
        return outputFormatter
    }
}

extension Numeric {
    func toCurrency(formatter: NumberFormatter = CurrencyFormatter.outputFormatter) -> String? {
        guard let num = self as? NSNumber else { return nil }
        var formatedSting = formatter.string(from: num)
        guard let locale = formatter.locale else { return formatedSting }
        if let separator = formatter.groupingSeparator, let localeValue = locale.groupingSeparator {
            formatedSting = formatedSting?.replacingOccurrences(of: localeValue, with: separator)
        }
        if let separator = formatter.decimalSeparator, let localeValue = locale.decimalSeparator {
            formatedSting = formatedSting?.replacingOccurrences(of: localeValue, with: separator)
        }
        return formatedSting
    }
}

Uso

let price = 12423.42
print(price.toCurrency() ?? "")

CurrencyFormatter.outputFormatter = CurrencyFormatter.create(style: .currencyISOCode)
print(price.toCurrency() ?? "nil")

CurrencyFormatter.outputFormatter = CurrencyFormatter.create(locale: Locale(identifier: "es_ES"))
print(price.toCurrency() ?? "nil")

CurrencyFormatter.outputFormatter = CurrencyFormatter.create(locale: Locale(identifier: "de_DE"), groupingSeparator: " ", style: .currencyISOCode)
print(price.toCurrency() ?? "nil")

CurrencyFormatter.outputFormatter = CurrencyFormatter.create(groupingSeparator: "_", decimalSeparator: ".", style: .currencyPlural)
print(price.toCurrency() ?? "nil")

let formatter = CurrencyFormatter.create(locale: Locale(identifier: "de_DE"), groupingSeparator: " ", decimalSeparator: ",", style: .currencyPlural)
print(price.toCurrency(formatter: formatter) ?? "nil")

Resultados

$12,423.42
USD12,423.42
12.423,42 €
12 423,42 EUR
12_423.42 US dollars
12 423,42 Euro
Vasily Bodnarchuk
fuente
3

Actualizado para Swift 4 de la respuesta de @Michael Voccola:

extension Double {
    var asLocaleCurrency: String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = Locale.current

        let formattedString = formatter.string(from: self as NSNumber)
        return formattedString ?? ""
    }
}

Nota: no hay desenvolvimientos forzados, desenvolvimientos forzados son malos.

kakubei
fuente
2

Swift 4 TextField implementado

var value = 0    
currencyTextField.delegate = self

func numberFormatting(money: Int) -> String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = .current
        return formatter.string(from: money as NSNumber)!
    }

currencyTextField.text = formatter.string(from: 50 as NSNumber)!

func textFieldDidEndEditing(_ textField: UITextField) {
    value = textField.text
    textField.text = numberFormatting(money: Int(textField.text!) ?? 0 as! Int)
}

func textFieldDidBeginEditing(_ textField: UITextField) {
    textField.text = value
}
Dary
fuente
0
extension Float {
    var convertAsLocaleCurrency :String {
        var formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = Locale.current
        return formatter.string(from: self as NSNumber)!
    }
}

Esto funciona para swift 3.1 xcode 8.2.1

du Phung
fuente
Si bien este fragmento de código es bienvenido y puede proporcionar algo de ayuda, sería mucho mejor si incluyera una explicación de cómo y por qué esto resuelve el problema. Recuerde que está respondiendo la pregunta para los lectores en el futuro, ¡no solo para la persona que pregunta ahora! Por favor, editar su respuesta para agregar explicación y dar una indicación de lo que se aplican limitaciones y supuestos.
Toby Speight
No use tipos flotantes para la moneda, use decimal.
adnako
0

Rápido 4

formatter.locale = Locale.current

si desea cambiar la configuración regional, puede hacerlo así

formatter.locale = Locale.init(identifier: "id-ID") 

// Esta es la configuración regional de Indonesia. si desea usarlo según el área del teléfono móvil, utilícelo según la mención superior Locale.current

//MARK:- Complete code
let formatter = NumberFormatter()
formatter.numberStyle = .currency
    if let formattedTipAmount = formatter.string(from: Int(newString)! as 
NSNumber) { 
       yourtextfield.text = formattedTipAmount
}
Shakeel Ahmed
fuente
0

agregar esta función

func addSeparateMarkForNumber(int: Int) -> String {
var string = ""
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .decimal
if let formattedTipAmount = formatter.string(from: int as NSNumber) {
    string = formattedTipAmount
}
return string
}

utilizando:

let giaTri = value as! Int
myGuessTotalCorrect = addSeparateMarkForNumber(int: giaTri)
codificadores
fuente