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

Swift中6位验证码UITextField首框退格功能异常问题排查

iPhone验证码输入框退格功能异常问题

我正在开发一款iPhone应用,用户需输入6位验证码,每位数字对应一个独立的UITextField,通过UIStackView以代码方式实现横向排列UI。但遇到退格功能异常问题:当光标处于第二个输入框时按下退格键,预期应跳转至第一个输入框并清空其内容,但该操作未生效,其他输入框的退格功能正常。

代码实现

import UIKit

class SignUpPhoneVerificationViewController: UIViewController, UITextFieldDelegate {
    
    let codeTextFieldStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.distribution = .fillEqually
        stackView.spacing = 10
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    private func setupUI() {
        view.addSubview(codeTextFieldStackView)
        
        for i in 0..<6 {
            let textField = BackspaceTextField()
            textField.borderStyle = .roundedRect
            textField.textAlignment = .center
            textField.font = UIFont.systemFont(ofSize: 24)
            textField.keyboardType = .numberPad
            textField.delegate = self
            textField.tag = i
            textField.translatesAutoresizingMaskIntoConstraints = false
            codeTextFieldStackView.addArrangedSubview(textField)
        }

        // Set up constraints for codeTextFieldStackView (not shown for brevity)
    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        // Only allow numbers
        guard CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string)) || string.isEmpty else {
            return false
        }

        if string.isEmpty {
            // Backspace was pressed
            if textField.text?.isEmpty ?? true {
                if let previousTextField = view.viewWithTag(textField.tag - 1) as? UITextField {
                    previousTextField.text = ""
                    previousTextField.becomeFirstResponder()
                } else {
                    // Move cursor to the first text field if it's the second field
                    if textField.tag == 1, let firstTextField = view.viewWithTag(0) as? UITextField {
                        firstTextField.text = ""
                       firstTextField.becomeFirstResponder()
                    }
                }
            } else {
                textField.text = ""
            }
            return false
        }

        if let text = textField.text, (text + string).count > 1 {
            return false
        }

        textField.text = string
        let nextTag = textField.tag + 1
        if let nextResponder = view.viewWithTag(nextTag) {
            nextResponder.becomeFirstResponder()
        } else {
            textField.resignFirstResponder()
        }
        return false
    }
}

class BackspaceTextField: UITextField {
    override func deleteBackward() {
        if text?.isEmpty ?? true {
            if let previousTextField = superview?.viewWithTag(tag - 1) as? UITextField {
                previousTextField.text = ""
                previousTextField.becomeFirstResponder()
            } else {
                // Move cursor to the first text field if it's the second field
                if tag == 1, let firstTextField = superview?.viewWithTag(0) as? UITextField {
                    firstTextField.text = ""
                    firstTextField.becomeFirstResponder()
                } else {
                    super.deleteBackward()
                }
            }
        } else {
           text = ""
        }
    }
}

已尝试的解决措施

  • 实现自定义UITextField类(BackspaceTextField)处理退格按键
  • 添加清除按钮重置所有输入框,但问题仍存在

预期与实际效果

  • 预期效果:光标在第二个输入框时按退格,跳转至第一个输入框并清空内容
  • 实际效果:光标未跳转,第一个输入框内容保留

问题原因与解决方案

问题核心在于自定义BackspaceTextField中使用superview?.viewWithTag(tag - 1)查找上一个输入框时,superview是UIStackView,而输入框的tag是在ViewController的根视图层级下设置的,StackView无法通过viewWithTag找到对应的输入框。

修复步骤:

  1. 修改自定义TextField的退格逻辑:
    调整BackspaceTextField的deleteBackward方法,通过遍历视图层级找到ViewController的根视图,再从中查找对应tag的输入框:

    class BackspaceTextField: UITextField {
        override func deleteBackward() {
            if text?.isEmpty ?? true {
                // 遍历找到ViewController的根视图
                var currentView = superview
                while currentView != nil, currentView?.next is UIViewController == false {
                    currentView = currentView?.superview
                }
                
                guard let parentView = currentView, tag > 0 else {
                    super.deleteBackward()
                    return
                }
                
                if let previousTextField = parentView.viewWithTag(tag - 1) as? UITextField {
                    previousTextField.text = ""
                    previousTextField.becomeFirstResponder()
                }
            } else {
                text = ""
            }
        }
    }
    
  2. 简化Delegate中的退格处理:
    由于自定义TextField已经处理了退格逻辑,删除ViewController代理方法中冗余的退格判断,避免双重处理冲突:

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        // Only allow numbers
        guard CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string)) || string.isEmpty else {
            return false
        }
    
        if string.isEmpty {
            // 退格操作交给自定义TextField处理
            return true
        }
    
        if let text = textField.text, (text + string).count > 1 {
            return false
        }
    
        textField.text = string
        let nextTag = textField.tag + 1
        if let nextResponder = view.viewWithTag(nextTag) {
            nextResponder.becomeFirstResponder()
        } else {
            textField.resignFirstResponder()
        }
        return false
    }
    

这样修改后,当第二个输入框为空时按下退格,就能正确跳转到第一个输入框并清空内容了。

内容的提问来源于stack exchange,提问作者TheGuyWhoCantCode123

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 23:23:11