swiftstringuikituitextfieldnsnumberformatter

How to format numbers in a textField with math equation string?


I'm trying to format numbers in a UITextField consists of math equation string: "number + number".

At the moment I can type just a single number, then convert it to Double -> format with NSNumberFormatter -> convert back to String -> assign to textField.text:

The code:

   func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.locale = .current
formatter.roundingMode = .down

let numberString = textField.text ?? ""
guard let range = Range(range, in: numberString) else { return false }
let updatedString = numberString.replacingCharacters(in: range, with: string)
let correctDecimalString = updatedString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "")

guard let value = Double(completeString) else { return false }

let formattedNumber = formatter.string(for: value)
textField.text = formattedNumber

return string == formatter.decimalSeparator
}

Now I want to add a calculation functionality and display a simple math equation in a textField as "number + number", but each number should be formatted as shown above. Example (but without formatting):

I can't properly implement that. The logic for me was: track the String each time new char inserts -> if it has math sign extract numbers -> convert them to Double -> format with NSNumberFormatter -> convert back to String -> construct a new String "number + number".

The code I tried:

        if let firstString = completeString.split(separator: "+").first, let secondString = completeString.split(separator: "+").last {
        guard let firstValue = Double(firstString) else { return false }
        guard let secondValue = Double(secondString) else { return false }

        let firstFormattedNumber = formatter.string(for: firstValue)
        let secondFormattedNumber = formatter.string(for: secondValue)

        textField.text = "\(firstFormattedNumber ?? "") + \(secondFormattedNumber ?? "")"

    // another try
    if completeString.contains("+") {
        let stringArray = completeString.components(separatedBy: "+")
        for character in stringArray {
            print(character)
            guard let value = Double(character) else { return false }
            guard let formattedNumber = formatter.string(for: value) else { return false }
            textField.text = "\(formattedNumber) + "
        }
    }

But it's not working properly. I tried to search but didn't find any similar questions.

Test project on GitHub

How can I format the numbers from such a string?


Solution

  • Here is how I was able to solve my question:

        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.maximumFractionDigits = 2
        formatter.locale = .current
        formatter.roundingMode = .down
        
        //set of possible math operations
        let symbolsSet = Set(["+","-","x","/"])
    
        let numberString = textField.text ?? ""
        guard let range = Range(range, in: numberString) else { return false }
        let updatedString = numberString.replacingCharacters(in: range, with: string)
        let correctDecimalString = updatedString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
        let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "")
    
        //receive math symbol user typed
        let symbol = symbolsSet.filter(completeString.contains).first ?? ""
    
        //receive number of symbols in a String. If user wants to type more than one math symbol - do not insert
        let amountOfSymbols = completeString.filter({String($0) == symbol}).count
        if amountOfSymbols > 1 { return false }
    
        //receive numbers typed by user
        let numbersArray = completeString.components(separatedBy: symbol)
        //check for each number - if user wants to type more than one decimal sign - do not insert
        for number in numbersArray {
            let amountOfDecimalSigns = number.filter({$0 == "."}).count
            if amountOfDecimalSigns > 1 { return false }
        }
        guard let firstNumber = Double(String(numbersArray.first ?? "0")) else { return true }
        guard let secondNumber = Double(String(numbersArray.last ?? "0")) else { return true }
        
        let firstFormattedNumber = formatter.string(for: firstNumber) ?? ""
        let secondFormattedNumber = formatter.string(for: secondNumber) ?? ""
        // if user typed math symbol - show 2 numbers and math symbol, if not - show just first typed number
        textField.text = completeString.contains(symbol) ? "\(firstFormattedNumber)\(symbol)\(secondFormattedNumber)" : "\(firstFormattedNumber)"
        
        return string == formatter.decimalSeparator
    }