Борьба с NSNumberFormatter в Swift за валюту


86

Я создаю бюджетное приложение, которое позволяет пользователю вводить свой бюджет, а также транзакции. Мне нужно разрешить пользователю вводить как пенсы, так и фунты из отдельных текстовых полей, и они должны быть отформатированы вместе с символами валюты. На данный момент у меня это работает нормально, но я бы хотел сделать его локализованным, поскольку в настоящее время он работает только с фунтами стерлингов. Я изо всех сил пытался скрыть примеры NSNumberFormatter от Objective C до Swift.

Моя первая проблема заключается в том, что мне нужно установить заполнители для полей ввода, специфичные для местоположения пользователя. Например. Фунты и пенсы, доллары и центы и т. Д.

Вторая проблема заключается в том, что значения, вводимые в каждое из текстовых полей, таких как 10216 и 32, должны быть отформатированы, и необходимо добавить символ валюты, специфичный для местоположения пользователя. Таким образом, получилось бы 10216,32 фунта стерлингов или 10216,32 доллара США и т. Д.

Кроме того, мне нужно использовать результат отформатированного числа в вычислении. Итак, как я могу сделать это, не столкнувшись с проблемами, не столкнувшись с проблемами с символом валюты?

Любая помощь приветствуется.


2
можешь выложить пример неработающего кода?
NiñoScript

Ответы:


205

Вот пример того, как использовать его в Swift 3. ( Edit : работает и в 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 €"

Вот старый пример того, как его использовать в 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 €"

Спасибо. Я отредактировал свой вопрос и уточнил.
user3746428

Основываясь на приведенном вами примере, мне удалось реализовать форматирование чисел в моей программе, чтобы этот бит был отсортирован. Теперь мне просто нужно выяснить, как установить заполнители текстового поля в зависимости от местоположения пользователя.
user3746428

2
Нет необходимости приводить его к NSNumber, вы можете использовать строку func метода форматирования (для obj: Any?) -> String ?. Так что вам просто нужно использовать string(for: price)вместоstring(from: price)
Лео Дабус

1
@LeoDabus, вы правы, я не знал об этом методе, я не уверен, следует ли мне редактировать свой ответ, поскольку я думаю, что я бы предпочел использовать API NumberFormatter и явным образом использовать NSNumber, а не позволять ему неявно бросьте его внутрь.
NiñoScript

Обратите внимание, что результатом formatter.string (from :) является необязательная строка, а не строка (как подразумевается в комментариях), поэтому перед использованием необходимо развернуть ее.
Али Бидл

25

Swift 3:

Если вы ищете решение, которое даст вам:

  • "5" = "5 долларов"
  • "5.0" = "5 долларов"
  • "5.00" = "5 долларов"
  • "5.5" = "5,50 доллара"
  • "5.50" = "5.50 долларов"
  • "5.55" = "5.55 доллара"
  • "5.234234" = "5.23"

Пожалуйста, используйте следующее:

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

Нет необходимости инициализировать новый объект NSNumber вы можете использовать форматировщик метод func string(for obj: Any?) -> String?вместоstring(from:)
Лео Dabus

19

Я также реализовал решение, предоставленное @ NiñoScript, как расширение:

Расширение

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

Применение:

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

Расширение должно расширять FloatingPoint для версии Swift 3 и строки (from: method для NSNumber. Для типов FlotingPoint вам необходимо использовать метод string (for :). Я опубликовал расширение Swift 3
Лео Дабус,

Не используйте типы с плавающей запятой для валюты, используйте десятичную дробь.
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"

3
Не используйте типы с плавающей запятой для валюты, используйте десятичную дробь.
adnako


7

Детали

  • Xcode 10.2.1 (10E1001), Swift 5

Решение

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

Применение

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

Полученные результаты

$12,423.42
USD12,423.42
12.423,42 €
12 423,42 EUR
12_423.42 US dollars
12 423,42 Euro

3

Обновлено для Swift 4 из ответа @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 ?? ""
    }
}

Примечание: принудительное развертывание запрещено, принудительное развертывание - зло.


2

Реализовано текстовое поле Swift 4

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
}

0
extension Float {
    var convertAsLocaleCurrency :String {
        var formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = Locale.current
        return formatter.string(from: self as NSNumber)!
    }
}

Это работает для swift 3.1 xcode 8.2.1


Хотя этот фрагмент кода приветствуется и может оказать некоторую помощь, его можно было бы значительно улучшить, если бы он включал объяснение того, как и почему это решает проблему. Помните, что вы отвечаете на вопрос будущих читателей, а не только человеку, который задает его сейчас! Пожалуйста , измените свой ответ , чтобы добавить объяснение, и дать указание о том , что применять ограничения и допущения.
Тоби Спейт

Не используйте типы с плавающей запятой для валюты, используйте десятичную дробь.
adnako

0

Swift 4

formatter.locale = Locale.current

если вы хотите изменить язык, вы можете сделать это так

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

// Это локаль для локали Индонезии. если вы хотите использовать в соответствии с областью мобильного телефона, используйте его в соответствии с верхним упоминанием Locale.current

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

0

добавить эту функцию

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
}

с помощью:

let giaTri = value as! Int
myGuessTotalCorrect = addSeparateMarkForNumber(int: giaTri)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.