Lutte avec NSNumberFormatter dans Swift pour la monnaie

86

Je crée une application de budget qui permet à l'utilisateur de saisir son budget ainsi que ses transactions. Je dois autoriser l'utilisateur à saisir à la fois les pence et les livres dans des champs de texte séparés et ils doivent être formatés avec des symboles monétaires. Cela fonctionne bien pour le moment, mais je voudrais le rendre localisé car actuellement, il ne fonctionne qu'avec GBP. J'ai eu du mal à dissimuler des exemples NSNumberFormatter de l'Objectif C à Swift.

Mon premier problème est le fait que je dois définir les espaces réservés pour que les champs de saisie soient spécifiques à l'emplacement des utilisateurs. Par exemple. Livres et pence, dollars et cents etc ...

Le deuxième problème est que les valeurs entrées dans chacun des champs de texte tels que 10216 et 32 ​​doivent être formatées et le symbole monétaire spécifique à l'emplacement des utilisateurs doit être ajouté. Cela deviendrait donc 10216,32 £ ou 10216,32 $ etc ...

De plus, je dois utiliser le résultat du nombre formaté dans un calcul. Alors, comment puis-je faire cela sans rencontrer de problèmes sans rencontrer de problèmes avec le symbole monétaire?

Toute aide serait très appréciée.

user3746428
la source
2
pouvez-vous poster un exemple de code non fonctionnel?
NiñoScript

Réponses:

205

Voici un exemple sur la façon de l'utiliser sur Swift 3. ( Edit : fonctionne également dans 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 €"

Voici l'ancien exemple sur la façon de l'utiliser sur 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
la source
Merci. J'ai édité ma question et j'ai été plus précis.
user3746428
Sur la base de l'exemple que vous avez fourni, j'ai réussi à implémenter la mise en forme des nombres dans mon programme, de sorte que le bit est trié. Maintenant, j'ai juste besoin de comprendre comment définir les espaces réservés du champ de texte en fonction de l'emplacement des utilisateurs.
user3746428
2
Pas besoin de le convertir en NSNumber, vous pouvez utiliser la méthode du formateur func string (pour obj: Any?) -> String ?. Il vous suffit donc d'utiliser à la string(for: price)place destring(from: price)
Leo Dabus
1
@LeoDabus vous avez raison, je ne connaissais pas cette méthode, je ne sais pas si je devrais modifier ma réponse, car je pense que je préfère utiliser l'API de NumberFormatter et être explicite sur l'utilisation de NSNumber plutôt que de le laisser implicitement jetez-le à l'intérieur.
NiñoScript
Notez que le résultat de formatter.string (from :) est une chaîne facultative et non une chaîne (comme l'impliquent les commentaires), il devra donc être déballé avant utilisation.
Ali Beadle
25

Swift 3:

Si vous recherchez une solution qui vous donne:

  • "5" = "5 $"
  • "5.0" = "5 $"
  • "5,00" = "5 $"
  • "5,5" = "5,50 $"
  • "5,50" = "5,50 $"
  • "5,55" = "5,55 $"
  • "5.234234" = "5,23"

Veuillez utiliser ce qui suit:

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
la source
Pas besoin d'initialiser un nouvel objet NSNumber, vous pouvez utiliser la méthode du formateur à la func string(for obj: Any?) -> String?place destring(from:)
Leo Dabus
19

J'ai également implémenté la solution fournie par @ NiñoScript en tant qu'extension:

Extension

// 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)!
    }
}

Usage:

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
la source
L'extension doit étendre la version et la chaîne de FloatingPoint pour Swift 3 (de: method est pour NSNumber. Pour les types FlotingPoint, vous devez utiliser la méthode string (for :). J'ai publié une extension Swift 3
Leo Dabus
N'utilisez pas de types flottants pour la devise, utilisez des décimales.
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
la source
3
N'utilisez pas de types flottants pour la devise, utilisez des décimales.
adnako
7

Détails

  • Xcode 10.2.1 (10E1001), Swift 5

Solution

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
    }
}

Usage

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")

Résultats

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

Mise à jour pour Swift 4 à partir de la réponse 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 ?? ""
    }
}

Remarque: pas de déballage forcé, le déballage forcé est maléfique.

kakubei
la source
2

Swift 4 TextField mis en œuvre

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
la source
0
extension Float {
    var convertAsLocaleCurrency :String {
        var formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = Locale.current
        return formatter.string(from: self as NSNumber)!
    }
}

Cela fonctionne pour swift 3.1 xcode 8.2.1

du Phung
la source
Bien que cet extrait de code soit le bienvenu et puisse fournir une aide, il serait grandement amélioré s'il incluait une explication sur la manière et la raison pour laquelle cela résout le problème. N'oubliez pas que vous répondez à la question des lecteurs à l'avenir, pas seulement à la personne qui la pose maintenant! Veuillez modifier votre réponse pour ajouter une explication et donner une indication des limites et des hypothèses applicables.
Toby Speight
N'utilisez pas de types flottants pour la devise, utilisez des décimales.
adnako
0

Swift 4

formatter.locale = Locale.current

si vous voulez changer de langue, vous pouvez le faire comme ceci

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

// Il s'agit de la langue de la langue de l'Indonésie. si vous souhaitez utiliser selon la zone de téléphone mobile, utilisez-le selon la mention supérieure Locale.

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

ajouter cette fonction

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
}

en utilisant:

let giaTri = value as! Int
myGuessTotalCorrect = addSeparateMarkForNumber(int: giaTri)
codeurs
la source