You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何替换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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 01:09:29