I have a textField
where I can type numbers (2 333,44) or simple math equations (12+3, 11x3,5). For now I can modify the textField.text
with adding a new string
only to the end of existing text.
If I move cursor
to any position inside number
it will add a new character
only at the end: https://i.stack.imgur.com/HzaA9.gif
How to modify the code to insert new characters
at any cursor
position?
My current code from shouldChangeCharactersIn:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 1
formatter.locale = .current
formatter.roundingMode = .down
let numberString = "\(textField.text ?? "")".replacingOccurrences(of: formatter.groupingSeparator, with: "")
let lastCharacter = numberString.last ?? "."
var symbol: String {
var tempArray = [String]()
let mathSymbols = "+-÷x"
for character in numberString {
if mathSymbols.contains(character) {
tempArray.append(String(character))
}
}
return tempArray.last ?? ""
}
var numbersArray: [String] {
if numberString.first == "-" {
let positiveString = numberString.dropFirst()
var tempArray = positiveString.components(separatedBy: symbol)
tempArray[0] = "-\(tempArray[0])"
return tempArray
} else {
return numberString.components(separatedBy: symbol)
}
}
//turn numbers into Float and create a String from them to be able to receive a correct result from NSExpression
var floatNumberArray: [Float] {
if numberString.first == "-" {
let tempString = lastCharacter == Character(formatter.decimalSeparator) ? numberString.dropFirst().dropLast() : numberString.dropFirst()
var tempArray = tempString.components(separatedBy: symbol)
tempArray[0] = "-\(tempArray[0])"
return tempArray.compactMap { Float($0.replacingOccurrences(of: formatter.decimalSeparator, with: ".")) }
} else {
return numberString.components(separatedBy: symbol).compactMap { Float($0.replacingOccurrences(of: formatter.decimalSeparator, with: ".")) }
}
}
var floatNumberString: String {
if numberString.contains("x") {
return "\(floatNumberArray.first ?? 0)\("*")\(floatNumberArray.last ?? 0)"
} else if numberString.contains("÷") {
return "\(floatNumberArray.first ?? 0)\("/")\(floatNumberArray.last ?? 0)"
} else {
return "\(floatNumberArray.first ?? 0)\(symbol)\(floatNumberArray.last ?? 0)"
}
}
let amountOfDecimals = "\(numbersArray.last ?? "")\(string)".filter({ $0 == Character(formatter.decimalSeparator) }).count
//allow to insert 0 after the decimal symbol to avoid formatting i.e. 2.04
formatter.minimumFractionDigits = numberString.last == Character(formatter.decimalSeparator) && string == "0" ? 1 : 0
//allow string to be modified by backspace button
if string == "" { return false }
//allow numbers as a first character, except math symbols
if numberString == "" {
if Character(string).isNumber {
textField.text = string
} else {
return false
}
}
//allow only one decimal symbol per number
else if amountOfDecimals > 1 { return false }
if numbersArray.count > 1 {
//if number is entered
if Character(string).isNumber {
textField.text = "\(formatter.string(for: Double("\(numbersArray.first?.replacingOccurrences(of: formatter.decimalSeparator, with: ".") ?? "")") ?? 0) ?? "")\(symbol)\(formatter.string(for: Double("\(numbersArray.last?.replacingOccurrences(of: formatter.decimalSeparator, with: ".") ?? "")\(string)") ?? 0) ?? "")"
//if symbol is entered
} else if string == formatter.decimalSeparator {
textField.text = "\(textField.text ?? "")\(string)"
} else {
//perform calculation if last entered character is a number
if lastCharacter.isNumber {
let result = performCalculation(format: floatNumberString)
textField.text = string == "=" ? "\(formatter.string(from: result) ?? "")" : "\(formatter.string(from: result) ?? "")\(string)"
//perform calculation if last entered character is a decimal symbol
} else if lastCharacter == Character(formatter.decimalSeparator) {
let result = performCalculation(format: floatNumberString)
textField.text = string == "=" ? "\(formatter.string(from: result) ?? "")" : "\(formatter.string(from: result) ?? "")\(string)"
//change math symbol before enter a second number
} else {
textField.text = "\(textField.text?.dropLast() ?? "")\(string)"
}
}
} else {
//if number is entered
if Character(string).isNumber {
textField.text = "\(formatter.string(for: Double("\(numbersArray.first?.replacingOccurrences(of: formatter.decimalSeparator, with: ".") ?? "")\(string)") ?? 0) ?? "")"
} else {
//if math symbol is entered
if lastCharacter.isNumber {
textField.text = "\(textField.text ?? "")\(string)"
}
}
}
return false
}
func performCalculation(format: String) -> NSNumber {
let expression = NSExpression(format: format)
let answer = expression.expressionValue(with: nil, context: nil)
return answer as! NSNumber
}
}
My guess is to modify the string
with a range.
So I tried:
1.Create range
let numberString = "\(textField.text ?? "")".replacingOccurrences(of: formatter.groupingSeparator, with: "")
guard let range = Range(range, in: numberString) else { return false }
2.Remove string
at the end and use range
instead
} else {
//if number is entered
if Character(string).isNumber {
textField.text = "\(formatter.string(for: Double("\(numbersArray.first?.replacingOccurrences(of: formatter.decimalSeparator, with: ".").replacingCharacters(in: range, with: string) ?? "") ?? "")") ?? 0) ?? "")"
} else {
//if math symbol is entered
if lastCharacter.isNumber {
textField.text = "\(textField.text?.replacingCharacters(in: range, with: string) ?? "") ?? "")"
}
}
}
return false
}
3.Then I can insert at any position but can add only 4 numbers if add from keyboard and insert more from another cursor
position: https://i.stack.imgur.com/iDV4n.gif