Как ввести формат валюты в текстовое поле (справа налево) с помощью Swift?

У меня есть номер, скажем 0.00,

  • Когда пользователь нажимает 1. Мы должны иметь 0.01
  • Когда пользователь нажимает 2. Мы должны отобразить 0.12
  • Когда пользователь нажимает 3. Мы должны отобразить 1.23
  • Когда пользователь нажимает 4. Мы должны отобразить 12.34

Как я могу сделать это с Swift?

6 ответов

Решение

Для Свифта 3. Введите формат валюты в текстовом поле (справа налево)

override func viewDidLoad() {
    super.viewDidLoad()

    textField.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
}

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

Вы можете создать текстовое поле с подклассами UITextField. Добавьте цель для UIControlEvents .editingChanged. Добавьте метод селектора, чтобы отфильтровать цифры из строки текстового поля. После фильтрации всех не цифр из вашей строки вы можете снова отформатировать свой номер с помощью NumberFormatter следующим образом:

Swift 4.1 или позже

class CurrencyField: UITextField {
    var string: String { return text ?? "" }
    var decimal: Decimal {
        return string.digits.decimal /
            Decimal(pow(10, Double(Formatter.currency.maximumFractionDigits)))
    }
    var decimalNumber: NSDecimalNumber { return decimal.number }
    var doubleValue: Double { return decimalNumber.doubleValue }
    var integerValue: Int { return decimalNumber.intValue   }
    let maximum: Decimal = 999_999_999.99
    private var lastValue: String?
    override func willMove(toSuperview newSuperview: UIView?) {
        // you can make it a fixed locale currency if needed
        // Formatter.currency.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
        addTarget(self, action: #selector(editingChanged), for: .editingChanged)
        keyboardType = .numberPad
        textAlignment = .right
        editingChanged()
    }
    override func deleteBackward() {
        text = string.digits.dropLast().string
        editingChanged()
    }
    @objc func editingChanged(_ textField: UITextField? = nil) {
        guard decimal <= maximum else {
            text = lastValue
            return
        }
        text = Formatter.currency.string(for: decimal)
        lastValue = text
    }
}

extension NumberFormatter {
    convenience init(numberStyle: Style) {
        self.init()
        self.numberStyle = numberStyle
    }
}
extension Formatter {
    static let currency = NumberFormatter(numberStyle: .currency)
}
extension String {
    var digits: [UInt8] {
        return map(String.init).compactMap(UInt8.init)
    }
}
extension Collection where Element == UInt8 {
    var string: String { return map(String.init).joined() }
    var decimal: Decimal { return Decimal(string: string) ?? 0 }
}
extension Decimal {
    var number: NSDecimalNumber { return NSDecimalNumber(decimal: self) }
}

Я начал с ответа Лео Дабуса (который не сработал для меня из коробки) и в процессе попыток упростить и заставить его работать закончился этим, что я считаю довольно скудным и чистым, если я так скажу себя

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

Попробуйте этот кусок кода:

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

Мой последний код спасибо за вашу помощь

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

Спасибо всем присутствующим. Из всех ответов здесь мне удалось выйти со своим.

Сначала я установил начальное значение textField:

private func commonInit() { 
    amountTextField.text = "0.00"
}

Затем я использую UITextFieldDelegate для получения входного значения и текущего 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
}

Вот моя частная функция для форматирования числа, которое нужно перемещать справа налево:

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
}

Это в основном это. Другие дополнительные реализации могут быть добавлены по вашей инициативе. Сообщите мне, если возникнут проблемы с моей реализацией.

PS: Это, конечно, работает только для определенной валюты, в моем случае мои приложения настроены только для этой локальной валюты, поэтому я использую этот способ.

После множества проб и ошибок с предложенными ответами я нашел довольно простое решение:

Настройку для textField необходимо вызвать в настройке вашего представления.

В операторе switch, если пользователь вводит число от 0 до 9, это число добавляется к предыдущему строковому значению. Регистр по умолчанию охватывает кнопку Backspace и удаляет последний символ из строки.

Локаль для numberFormatter установлена ​​на текущую, поэтому он работает с разными валютами.

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

Вот код для 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
    }

Примечание. Подключите делегат для textField из раскадровки или программно

Просто для удовольствия: скопировал ответ Томаса (полные кредиты - и очки - ему, пожалуйста) в файл для запуска в виде скрипта Swift 4.1 (с небольшими исправлениями):

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)

Затем запустите его в терминале:

$ chmod +x dotnum.swift
$ ./dotnum.swift
0.01
0.21
3.21
43.21
Другие вопросы по тегам