How to replace a specific character in a string inside a UITextField?

I have a custom textField's input view - it is a Numpad style keyboard. Numpad is using to add numbers and math symbols to a textField.

I can't figure out how can I change a math symbol in a string if user already add one and wants to change it on another straight away. Here is an example of what I need: https://i.stack.imgur.com/IVGId.gif

Here is the code I use:

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

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

//all possible math operation symbols user can add
let symbolsSet = Set(["+","-","x","/"])
var amountOfSymbols = 0

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.groupingSeparator, with: "")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")

//current math symbol user add
let symbol = symbolsSet.filter(completeString.contains).last ?? ""
//if user add math symbol to an empty string - do not insert
if string == symbol, numberString.count == 0 { return false }

//count how much math symbols string has. If more that one - do not insert, string can have only one
completeString.forEach { character in
    if symbolsSet.contains(String(character)) {
        amountOfSymbols += 1
    }
}
if amountOfSymbols > 1 { return false }

//count how much decimals string has. If more that one - do not insert because it can have only one per number
let numbersArray = completeString.components(separatedBy: symbol)
for number in numbersArray {
    let amountOfDecimalSigns = number.filter({$0 == "."}).count
    if amountOfDecimalSigns > 1 { return false }
}

//create numbers from a string
guard let firstNumber = Double(String(numbersArray.first ?? "0")) else { return true }
guard let secondNumber = Double(String(numbersArray.last ?? "0")) else { return true }

//format numbers and turn them back to string
let firstFormattedNumber = formatter.string(for: firstNumber) ?? ""
let secondFormattedNumber = formatter.string(for: secondNumber) ?? ""

//assign formatted numbers to a textField
textField.text = completeString.contains(symbol) ? "\(firstFormattedNumber)\(symbol)\(secondFormattedNumber)" : "\(firstFormattedNumber)"

return string == formatter.decimalSeparator
}

The logic for me was to use textField.deleteBackwards() method to delete an old one and add a new math symbol after, but with above code it doesn't work: it deletes symbol, but a new one doesn't appear - I should press again so new symbol can appear.

What should I do to change a math symbol in a string?

Test project on GitHub

I suspect problem to be here:

let symbol = symbolsSet.filter(completeString.contains).last ?? ""

Could you print some log and tell what you get when you type second symbol ?:

print("symbol", symbol)
print("string", string)
print("updatedString", updatedString)
print("correctDecimalString", correctDecimalString)
let completeString = correctDecimalString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
print("completeString", completeString)

@Claude31

The closer I can get is to use the code:


var completeString = correctDecimalString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")

 if amountOfSymbols > 1 {

    let newSymbol = completeString.last?.description ?? ""
    completeString.removeAll { symbolsSet.contains(String($0))}
    textField.text = completeString+newSymbol

    return false

 }

But then If I'll add math symbol again it will change the first number which I want to avoid: https://share.cleanshot.com/tmFiAr

That's a usual problem with shouldChangeCharactersIn. The string tou use is not updated yet as you expect.

So please first answer my question and tell what you get in console, precisely, and what you typed when problem occurs.

print("symbol", symbol)
print("string", string)
print("updatedString", updatedString)
print("correctDecimalString", correctDecimalString)
let completeString = correctDecimalString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
print("completeString", completeString)

As for your last try, you should not change textField.text in this func, but the textField IBOutlet that you modify.

@Claude31

Here is what I get in console:

  1. Put print statements before check on how much math symbols string has:
 func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

//all possible math operation symbols user can add
let symbolsSet = Set(["+","-","x","/"])
var amountOfSymbols = 0

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.groupingSeparator, with: "")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")

//current math symbol user add
let symbol = symbolsSet.filter(completeString.contains).last ?? ""
//if user add math symbol to an empty string - do not insert
if string == symbol, numberString.count == 0 { return false }

 print("symbol", symbol)
 print("string", string)
 print("updatedString", updatedString)
 print("correctDecimalString", correctDecimalString)
 print("completeString", completeString)

//count how much math symbols string has. If more that one - do not insert, string can have only one
completeString.forEach { character in
    if symbolsSet.contains(String(character)) {
        amountOfSymbols += 1
    }
}
if amountOfSymbols > 1 { return false }
}

Here is the console output after each character I type:

Press 2

symbol 
string 2
updatedString 2
correctDecimalString 2
completeString 2

Press +

symbol +
string +
updatedString 2+
correctDecimalString 2+
completeString 2+

Press -

symbol +
string -
updatedString 2+-
correctDecimalString 2+-
completeString 2+-

2.Put print statements after check on how much math symbols string has:

if amountOfSymbols > 1 { return false }

  print("symbol", symbol)
  print("string", string)
  print("updatedString", updatedString)
  print("correctDecimalString", correctDecimalString)
  print("completeString", completeString)

Here is the console output after each character I type:

Press 2

symbol 
string 2
updatedString 2
correctDecimalString 2
completeString 2

Press +

symbol +
string +
updatedString 2+
correctDecimalString 2+
completeString 2+

Press -

Nothing is printed

How to replace a specific character in a string inside a UITextField?
 
 
Q