如何将 NumberFormatter 集成到一个简单的计算器应用程序?

问题描述 投票:0回答:1

我正在尝试更新现有代码以使用逗号进行分组分隔符(即 10,000,000 而不是 10000000),如何用简单的方法实现此目的?我正在发布现有的代码。

我想要的只是在活动计算窗口(textFieldMain)和历史窗口(textViewHistory)中显示带逗号的格式化数字

import UIKit

class ViewController: UIViewController {

    // MARK: Properties
    @IBOutlet weak var textFieldMain: UITextField!
    @IBOutlet weak var button0: UIButton!
    @IBOutlet weak var button1: UIButton!
    @IBOutlet weak var button2: UIButton!
    @IBOutlet weak var button3: UIButton!
    @IBOutlet weak var button4: UIButton!
    @IBOutlet weak var button5: UIButton!
    @IBOutlet weak var button6: UIButton!
    @IBOutlet weak var button7: UIButton!
    @IBOutlet weak var button8: UIButton!
    @IBOutlet weak var button9: UIButton!
    @IBOutlet weak var buttonPeriod: UIButton!
    @IBOutlet weak var buttonPlusMinus: UIButton!
    @IBOutlet weak var buttonEqual: UIButton!
    @IBOutlet weak var buttonAddition: UIButton!
    @IBOutlet weak var buttonSubstraction: UIButton!
    @IBOutlet weak var buttonMultiplication: UIButton!
    @IBOutlet weak var buttonDivision: UIButton!
    @IBOutlet weak var buttonAllCancel: UIButton!
    @IBOutlet weak var textViewHistory: UITextView!
    
    var valueA: Double = 0
    var valueB: Double = 0
    var currentOperator: String = ""
    var refreshTextField: Bool = true
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // MARK: Actions
    @IBAction func allCancel(_ sender: UIButton) {
        // reset all variables
        valueA = 0
        valueB = 0
        currentOperator = ""
        refreshTextField = true
        
        // update text field
        textFieldMain.text = "0"
    }
    @IBAction func clearHistory(_ sender: UIButton) {
           // reset all variables
         
           textViewHistory.text = ""
       }
    @IBAction func numberPress(_ sender: UIButton) {
        // find text to be added
        var value = sender.currentTitle!
        // get current text
        var currentText = ""
        
        // value shourld be refreshed?
        if textFieldMain.text != "0" && (!refreshTextField  || value == "±") {
            currentText = textFieldMain.text!
        }
        
        // zero
        if currentText == "0" && value == "0" {
            value = ""
        }
        
        // period
        if value == "." {
            if currentText == "" {
                value = "0."
            } else if currentText.range(of: ".") != nil {
                value = ""
            }
        }
        
        // plus/minus
        if value == "±" {
            value = ""
            if currentText != "" {
                if currentText.hasPrefix("-") {
                    let start = currentText.startIndex
                    let end = currentText.index(after: start)
                    currentText = currentText.replacingOccurrences(of: "-", with: "", range: start..<end)
                } else {
                    currentText = "-\(currentText)"
                }
            }
        }
        
        // update text field
        refreshTextField = false
        textFieldMain.text = "\(currentText)\(value)"
    }
    
    @IBAction func operatorPress(_ sender: UIButton) {
        // ignore if text field is empty
        if textFieldMain.text != "" {
            // get new operator
            let newOperator = sender.titleLabel?.text!
            // refresh text field
            refreshTextField = true
            
            if currentOperator == "" && newOperator != "=" {
                // copy current value
                valueA = Double(textFieldMain.text!)!
                
                // update operator
                currentOperator = newOperator!
            } else {
                // copy current value
                valueB = Double(textFieldMain.text!)!
                
                // perform operation
                var result: Double = 0
                switch currentOperator {
                case "+":
                    result = valueA + valueB
                case "-":
                    result = valueA - valueB
                case "×":
                    result = valueA * valueB
                case "÷":
                    result = valueA / valueB
                default:
                    result = valueB
                }
                
                if currentOperator != "" {
                    // construct history entry
                    let historyEntry: String = "\(valueA) \(currentOperator) \(valueB) = \(result)\n"
                    // update history
                    textViewHistory.text! += historyEntry
                }
                
                // update operator & values
                currentOperator = ""
                valueA = 0
                valueB = 0
                
                if newOperator != "=" {
                    valueA = result
                    currentOperator = newOperator!
                }
                
                // update text field
                textFieldMain.text = String(result)
            }
        }
    }
    
}

所以我尝试制定这些格式规则...

    private let auxFormatter: NumberFormatter = {
    let formatter = NumberFormatter()
    let locale = Locale.current
    formatter.groupingSeparator = ""
    formatter.decimalSeparator = locale.decimalSeparator
    formatter.numberStyle = .decimal
    formatter.maximumIntegerDigits = 100
    formatter.minimumFractionDigits = 0
    formatter.maximumFractionDigits = 100
    return formatter
}()
private let auxTotalFormatter: NumberFormatter = {
    let formatter = NumberFormatter()
    formatter.groupingSeparator = ""
    formatter.decimalSeparator = ""
    formatter.numberStyle = .decimal
    formatter.maximumIntegerDigits = 100
    formatter.minimumFractionDigits = 0
    formatter.maximumFractionDigits = 100
    return formatter
}()

private let printFormatter: NumberFormatter = {
    let formatter = NumberFormatter()
    let locale = Locale.current
    formatter.groupingSeparator = locale.groupingSeparator
    formatter.decimalSeparator = locale.decimalSeparator
    formatter.numberStyle = .decimal
    formatter.maximumIntegerDigits = 9
    formatter.minimumFractionDigits = 0
    formatter.maximumFractionDigits = 8
    return formatter
}() 

对代码进行以下修改

@IBAction func numberPress(_ sender: UIButton) {
    var value = sender.currentTitle!
    
    if textFieldMain.text != "0" && (!refreshTextField || value == "±") {
        value = textFieldMain.text!
    }
    
    if value == "." {
        if textFieldMain.text == "" {
            value = "0."
        } else if textFieldMain.text!.contains(".") {
            return
        }
    }
    
    if value == "±" {
        if textFieldMain.text != "" {
            if textFieldMain.text!.hasPrefix("-") {
                textFieldMain.text!.remove(at: textFieldMain.text!.startIndex)
            } else {
                textFieldMain.text = "-" + textFieldMain.text!
            }
        }
        return
    }
    
    if refreshTextField {
        textFieldMain.text = value
    } else {
        textFieldMain.text! += value
    }
    
    refreshTextField = false
}

@IBAction func operatorPress(_ sender: UIButton) {
    if textFieldMain.text != "" {
        let newOperator = sender.titleLabel?.text!
        refreshTextField = true
        
        if currentOperator == "" && newOperator != "=" {
            valueA = auxFormatter.number(from: textFieldMain.text!)?.doubleValue ?? 0
            currentOperator = newOperator!
        } else {
            valueB = auxFormatter.number(from: textFieldMain.text!)?.doubleValue ?? 0
            
            var result: Double = 0
            switch currentOperator {
            case "+":
                result = valueA + valueB
            case "-":
                result = valueA - valueB
            case "×":
                result = valueA * valueB
            case "÷":
                result = valueA / valueB
            default:
                result = valueB
            }
            
            if currentOperator != "" {
                let historyEntry = "\(auxTotalFormatter.string(from: NSNumber(value: valueA))!) \(currentOperator) \(auxTotalFormatter.string(from: NSNumber(value: valueB))!) = \(printFormatter.string(from: NSNumber(value: result))!)\n"
                textViewHistory.text! += historyEntry
            }
            
            currentOperator = ""
            valueA = 0
            valueB = 0
            
            if newOperator != "=" {
                valueA = result
                currentOperator = newOperator!
            }
            
            textFieldMain.text = printFormatter.string(from: NSNumber(value: result))
        }
    }
}

但是现在所有输入的行为都是这样的...... 如果我按这三个数字 7 > 8 > 9 >,它会显示 77777777(如果我将其分解为 7 77 777 7777)

swift uiviewcontroller uikit
1个回答
0
投票

有很多方法可以实现这一点——您可能需要搜索现有的示例。

一些建议...

看起来您在值和字符串之间转换以及评估字符串太多了。

最好维护,并且仅在需要显示时才对其进行格式化。

因此,首先,我们可以沿着这些思路思考。

假设我们有

var curVal: Double
并且我们已经检查了按钮的
.currentTitle
并将其转换为
btnVal: Double
...

    curVal = 0
    
    // user taps "5"
    curVal *= 10.0
    curVal += btnVal
    // curVal now equals 5
    
    // user taps "3"
    curVal *= 10.0
    curVal += btnVal
    // curVal now equals 53

    // user taps "7"
    curVal *= 10.0
    curVal += btnVal
    // curVal now equals 537
    

因此,每次更改

curVal
的值时,我们都会使用
NumberFormatter
来更新“显示”标签。

如果我们输入负数,我们需要做一些不同的

curVal
操作。为此,如果 btnVal 小于零,我们将
减去
curVal

    // user taps "5"
    curVal *= 10.0
    curVal += curVal >= 0 ? btnVal : -btnVal
    // curVal now equals 5
    
    // user taps "+/-"
    curVal *= -1.0
    // curVal now equals -5
    
    // user taps "3"
    curVal *= 10.0
    curVal += curVal >= 0 ? btnVal : -btnVal
    // curVal now equals -53
    
    // user taps "7"
    curVal *= 10.0
    curVal += curVal >= 0 ? btnVal : -btnVal
    // curVal now equals -537
    
    // user taps "+/-" again
    curVal *= -1.0
    // curVal now equals 537
    

NumberFormatter
会在需要时自动添加“-”...所以不再需要查看字符串是否以“-”开头或需要插入“-”。

当然,如果我们尝试输入小数点后的数字,这是行不通的!

因此,我们需要跟踪“十进制数”并btnVal

除以10、100、1000等,然后再将其添加到
curVal

让我们看看我们的按钮点击处理程序可能会是什么样子:

@objc func btnTapped(_ sender: UIButton) { guard let title = sender.currentTitle else { return } // if it's a number button if let val = Double(title) { var p: Double = 0 var btnVal: Double = val // if "decimal count" is greater-than-Zero, // we need to divide the input value by 10, 100, 1000, etc if decCount > 0 { p = pow(10.0, Double(decCount)) btnVal /= p decCount += 1 } else { currentValue *= 10.0 } currentValue += currentValue >= 0 ? btnVal : -btnVal // if we have decimal values, we need to round the result // due to floating-point precision // e.g. if we enter "0" "." "1" "2" // the actual value is 0.12000000000000002048 // so we round to the current number of decimal places if p > 0 { currentValue = round(currentValue * p) / p } } // if decimal button tapped if title == decimalSym { if decCount == 0 { decCount = 1 decJustTapped = true } } if title == plusMinusSym { // we don't want to display "-0" // so only do this if... if currentValue != 0 { currentValue *= -1 } } // clear the current value and // reset decimal count if title == "C" { currentValue = 0 decCount = 0 } if operators.contains(title) { // perform operation... } udpateEntryLabel() } func udpateEntryLabel() { // this will handle entering "." followed by one or more Zeroes numFormatter.minimumFractionDigits = decCount - 1 var str = numFormatter.string(from: currentValue as NSNumber) ?? "0" // this will only be true if the decimal symbol button was tapped // but we do not yet have a fractional value if decJustTapped { decJustTapped = false str += "." } entryLabel.text = str }
这是一个快速、完整的示例...


简单按钮子类以获得“圆形”按钮

class PillButton: UIButton { override func layoutSubviews() { super.layoutSubviews() layer.cornerRadius = bounds.height * 0.5 } }


示例视图控制器:

class CalcVC: UIViewController { let entryLabel = UILabel() let numFormatter: NumberFormatter = { let nf = NumberFormatter() nf.numberStyle = .decimal // we want ".1" to be displayed as "0.1" nf.minimumIntegerDigits = 1 return nf }() var currentValue: Double = 0 var decJustTapped: Bool = false var decCount: Int = 0 // for clarity (tough to read division character and plus character) // and, you'll probably want to localize the decimal character let plusMinusSym: String = "±" let pctSym: String = "%" let divideSym: String = "+" let multiplySym: String = "X" let minusSym: String = "-" let plusSym: String = "+" let decimalSym: String = "." let equalsSym: String = "=" lazy var operators: [String] = [ pctSym, divideSym, multiplySym, minusSym, plusSym, equalsSym ] override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBlue let btn = UIButton() let btnVal: Double = 0 var curVal: Double = 0 // user taps "5" curVal *= 10.0 curVal += btnVal // cVal now equals 5 // user taps "3" curVal *= 10.0 curVal += btnVal // cVal now equals 53 // user taps "7" curVal *= 10.0 curVal += btnVal // cVal now equals 537 // user taps "5" curVal *= 10.0 curVal += btnVal // cVal now equals 5 // user taps "+/-" curVal *= -1.0 // cVal now equals -5 // user taps "3" curVal *= 10.0 curVal += curVal >= 0 ? btnVal : -btnVal // cVal now equals -53 // user taps "7" curVal *= 10.0 curVal += curVal >= 0 ? btnVal : -btnVal // cVal now equals -537 // user taps "+/-" again curVal *= -1.0 // cVal now equals 537 let titles: [[String]] = [ ["C", plusMinusSym, pctSym, divideSym], ["7", "8", "9", multiplySym], ["4", "5", "6", minusSym], ["1", "2", "3", plusSym], ["0", decimalSym, equalsSym], ] let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 6 stackView.addArrangedSubview(entryLabel) for (iRow, rowTitles) in titles.enumerated() { let rowStack = UIStackView() rowStack.axis = .horizontal rowStack.distribution = .fillEqually rowStack.spacing = 6 // handle bottom row differently because we have only 3 buttons if iRow == titles.count - 1 { let b = PillButton() b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside) b.setTitle(rowTitles[0], for: []) b.setTitleColor(.white, for: .normal) b.setTitleColor(.lightGray, for: .highlighted) b.backgroundColor = .darkGray rowStack.addArrangedSubview(b) let rowStack2 = UIStackView() rowStack2.axis = .horizontal rowStack2.distribution = .fillEqually rowStack2.spacing = 6 for j in 1..<rowTitles.count { let b = PillButton() b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside) b.setTitle(rowTitles[j], for: []) b.setTitleColor(.white, for: .normal) b.setTitleColor(.lightGray, for: .highlighted) b.backgroundColor = j == 1 ? .darkGray : .systemOrange b.heightAnchor.constraint(equalTo: b.widthAnchor, multiplier: 1.0).isActive = true rowStack2.addArrangedSubview(b) } rowStack.addArrangedSubview(rowStack2) } else { rowTitles.forEach { s in let b = PillButton() b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside) b.setTitle(s, for: []) b.setTitleColor(.white, for: .normal) b.setTitleColor(.lightGray, for: .highlighted) b.backgroundColor = .darkGray b.heightAnchor.constraint(equalTo: b.widthAnchor, multiplier: 1.0).isActive = true rowStack.addArrangedSubview(b) } } stackView.addArrangedSubview(rowStack) } // set background colors for non-darkGray buttons for i in 0..<stackView.arrangedSubviews.count - 1 { if let sv = stackView.arrangedSubviews[i] as? UIStackView, let v = sv.arrangedSubviews.last { v.backgroundColor = .systemOrange } } if let sv = stackView.arrangedSubviews[1] as? UIStackView { for i in 0..<3 { sv.arrangedSubviews[i].backgroundColor = .lightGray } } stackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stackView) let g = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0), stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0), stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0), ]) // label properties entryLabel.font = .monospacedDigitSystemFont(ofSize: 24.0, weight: .regular) entryLabel.backgroundColor = UIColor(white: 0.95, alpha: 1.0) entryLabel.textAlignment = .right entryLabel.text = "0" } @objc func btnTapped(_ sender: UIButton) { guard let title = sender.currentTitle else { return } // if it's a number button if let val = Double(title) { var p: Double = 0 var btnVal: Double = val // if "decimal count" is greater-than-Zero, // we need to divide the input value by 10, 100, 1000, etc if decCount > 0 { p = pow(10.0, Double(decCount)) btnVal /= p decCount += 1 } else { currentValue *= 10.0 } currentValue += currentValue >= 0 ? btnVal : -btnVal // if we have decimal values, we need to round the result // due to floating-point precision // e.g. if we enter "0" "." "1" "2" // the actual value is 0.12000000000000002048 // so we round to the current number of decimal places if p > 0 { currentValue = round(currentValue * p) / p } } // if decimal button tapped if title == decimalSym { if decCount == 0 { decCount = 1 decJustTapped = true } } if title == plusMinusSym { // we don't want to display "-0" // so only do this if... if currentValue != 0 { currentValue *= -1 } } // clear the current value and // reset decimal count if title == "C" { currentValue = 0 decCount = 0 } if operators.contains(title) { // perform operation... } udpateEntryLabel() } func udpateEntryLabel() { // this will handle entering "." followed by one or more Zeroes numFormatter.minimumFractionDigits = decCount - 1 var str = numFormatter.string(from: currentValue as NSNumber) ?? "0" // this will only be true if the decimal symbol button was tapped // but we do not yet have a fractional value if decJustTapped { decJustTapped = false str += "." } entryLabel.text = str } }
运行时看起来像这样:

请注意,仅实现了数字、小数、“C”和“+/-”按钮...我将把操作和“历史视图”任务留给您:)

© www.soinside.com 2019 - 2024. All rights reserved.