如何使用 Swift 在文本字段中输入货币格式(从右到左)?

How to input currency format on a text field (from right to left) using Swift?

我有一个数字,比方说 0.00

如何用 Swift 做到这一点?

试试这段代码:

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

您可以创建子类化 UITextField 的货币文本字段。为 UIControlEvents .editingChanged 添加一个目标。添加一个选择器方法来过滤文本字段字符串中的数字。从字符串中过滤掉所有非数字后,您可以使用 NumberFormatter 再次格式化您的号码,如下所示:

Xcode 11.5 • Swift 5.2 或更高版本

import UIKit

class CurrencyField: UITextField {
    var decimal: Decimal { string.decimal / pow(10, Formatter.currency.maximumFractionDigits) }
    var maximum: Decimal = 999_999_999.99
    private var lastValue: String?
    var locale: Locale = .current {
        didSet {
            Formatter.currency.locale = locale
            sendActions(for: .editingChanged)
        }
    }
    override func willMove(toSuperview newSuperview: UIView?) {
        // you can make it a fixed locale currency if needed
        // self.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
        Formatter.currency.locale = locale
        addTarget(self, action: #selector(editingChanged), for: .editingChanged)
        keyboardType = .numberPad
        textAlignment = .right
        sendActions(for: .editingChanged)
    }
    override func deleteBackward() {
        text = string.digits.dropLast().string
        // manually send the editingChanged event
        sendActions(for: .editingChanged)
    }
    @objc func editingChanged() {
        guard decimal <= maximum else {
            text = lastValue
            return
        }
        text = decimal.currency
        lastValue = text
    }
}

extension CurrencyField {
    var doubleValue: Double { (decimal as NSDecimalNumber).doubleValue }
}

extension UITextField {
     var string: String { text ?? "" }
}

extension NumberFormatter {
    convenience init(numberStyle: Style) {
        self.init()
        self.numberStyle = numberStyle
    }
}

private extension Formatter {
    static let currency: NumberFormatter = .init(numberStyle: .currency)
}

extension StringProtocol where Self: RangeReplaceableCollection {
    var digits: Self { filter (\.isWholeNumber) }
}

extension String {
    var decimal: Decimal { Decimal(string: digits) ?? 0 }
}

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

extension LosslessStringConvertible {
    var string: String { .init(self) }
}

视图控制器

class ViewController: UIViewController {

    @IBOutlet weak var currencyField: CurrencyField!
    override func viewDidLoad() {
        super.viewDidLoad()
        currencyField.addTarget(self, action: #selector(currencyFieldChanged), for: .editingChanged)
        currencyField.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
    }
    @objc func currencyFieldChanged() {
        print("currencyField:",currencyField.text!)
        print("decimal:", currencyField.decimal)
        print("doubleValue:",(currencyField.decimal as NSDecimalNumber).doubleValue, terminator: "\n\n")
    }
}

Sample project


SwiftUI 这个 post 的版本

我的最终代码感谢您的帮助

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([=10=])}.filter{ [=10=].toInt() != nil }.map{Character([=10=])} ) 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!)!)"

Swift3。在文本字段上输入货币格式(从右到左)

override func viewDidLoad() {
    super.viewDidLoad()

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

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

这是swift2

的代码
@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

我从 Leo Dabus 的回答开始(这对我来说不是开箱即用的)并且在尝试简化并使其工作的过程中最终得到了这个,我认为它非常精简和干净如果我自己这么说

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

感谢这里的每一个人。从这里的所有答案中,我设法得出了我的答案。

首先我将 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 之间的数字,则该数字会添加到前一个字符串值中。默认大小写覆盖退格键并从字符串中删除最后一个字符。

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