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: https://i.stack.imgur.com/4qQza.gif

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 on above screenshot. Example (but without formatting): https://i.stack.imgur.com/W7Jet.gif

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?

Answered by artexhibit in 720701022

Here is how I solved my problem:

   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
}

Even without doing anything but launch app, it crashes after a few seconds:

2022-07-16 14:28:21.799258+0200 CustomNumpad[10734:1005077] [plugin] AddInstanceForFactory: No factory registered for id <CFUUID 0x600002aa4e20> F8BB1C28-BAE8-11D6-9C31-00039315CD46

2022-07-16 14:28:30.771818+0200 CustomNumpad[10734:1005242] [] [14:28:30.771] CMSampleBufferSetDataFailed signalled err=-12744 (kCMSampleBufferError_Invalidated) (sbuf invalidated) at FigSampleBuffer.c:3836

Please first fix this crash before we can investigate more.

Accepted Answer

Here is how I solved my problem:

   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
}
How to format numbers in a textField with math equation string?
 
 
Q