如何替换UITextField字符串中的指定数学符号?
问题
为UITextField自定义了Numpad样式的输入视图,用于输入数字和数学符号。目前无法实现用户已输入一个数学符号后,直接将其替换为另一个符号的功能(期望效果为点击新符号时自动替换旧符号)。
当前使用的代码如下:
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 }
尝试过用textField.deleteBackwards()删除旧符号后添加新符号,但旧符号删除后,新符号需要再次点击才会出现,无法实现直接替换。
解决方案
核心思路:当用户输入新的数学符号时,先提取文本中已有的数字段(移除旧符号),直接用新符号拼接成合法字符串,再重新格式化后赋值给文本框,避免依赖删除操作的异步问题。
修改后的代码如下:
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 symbolsSet = Set(["+","-","x","/"]) let isInputSymbol = symbolsSet.contains(string) var numberString = textField.text ?? "" // 空字符串禁止输入符号 if isInputSymbol && numberString.isEmpty { return false } // 处理符号替换逻辑 if isInputSymbol { // 拆分出前后数字部分(跳过原有符号) let numberParts = numberString.split(whereSeparator: { symbolsSet.contains(String($0)) }) let firstNumberPart = numberParts.first ?? "" let secondNumberPart = numberParts.count > 1 ? numberParts[1] : "" // 构造带新符号的字符串 let newRawString = "\(firstNumberPart)\(string)\(secondNumberPart)" // 统一格式为小数点模式,方便后续校验 let normalizedString = newRawString .replacingOccurrences(of: formatter.groupingSeparator, with: "") .replacingOccurrences(of: formatter.decimalSeparator, with: ".") // 校验每个数字段的小数位数(最多1个) let splitNumbers = normalizedString.components(separatedBy: string) for numStr in splitNumbers { let decimalCount = numStr.filter({ $0 == "." }).count if decimalCount > 1 { return false } } // 格式化数字并更新文本框 guard let firstNum = Double(String(splitNumbers.first ?? "0")), let secondNum = Double(String(splitNumbers.last ?? "0")) else { textField.text = newRawString return false } let formattedFirstNum = formatter.string(for: firstNum) ?? "" let formattedSecondNum = formatter.string(for: secondNum) ?? "" textField.text = "\(formattedFirstNum)\(string)\(formattedSecondNum)" return false } // 非符号输入的原有逻辑保留 guard let validRange = Range(range, in: numberString) else { return false } let updatedString = numberString.replacingCharacters(in: validRange, with: string) let normalizedString = updatedString .replacingOccurrences(of: formatter.groupingSeparator, with: "") .replacingOccurrences(of: formatter.decimalSeparator, with: ".") let currentSymbol = symbolsSet.filter(normalizedString.contains).last ?? "" var symbolCount = 0 normalizedString.forEach { if symbolsSet.contains(String($0)) { symbolCount += 1 } } if symbolCount > 1 { return false } let splitNumbers = normalizedString.components(separatedBy: currentSymbol) for numStr in splitNumbers { let decimalCount = numStr.filter({ $0 == "." }).count if decimalCount > 1 { return false } } guard let firstNum = Double(String(splitNumbers.first ?? "0")), let secondNum = Double(String(splitNumbers.last ?? "0")) else { return true } let formattedFirstNum = formatter.string(for: firstNum) ?? "" let formattedSecondNum = formatter.string(for: secondNum) ?? "" textField.text = normalizedString.contains(currentSymbol) ? "\(formattedFirstNum)\(currentSymbol)\(formattedSecondNum)" : "\(formattedFirstNum)" return string == formatter.decimalSeparator }
关键改进点
- 专属符号替换分支:输入符号时直接拆分数字段,用新符号拼接,绕过原有多符号拦截逻辑,确保替换操作一次性完成。
- 避免异步问题:无需调用
deleteBackwards(),直接通过字符串拼接生成目标文本,解决了旧符号删除后新符号需重复点击的问题。 - 保留合法性校验:完整保留了小数位数限制、数字格式化的原有逻辑,保证输入的规范性。
内容的提问来源于stack exchange,提问作者artexhibit
相关产品推荐
相关产品推荐

