UITextField 退格键对于 Swift 中输入的 6 位验证码中的第一个占位符无法正常工作

问题描述 投票:0回答:1

我正在构建一个 iPhone 应用程序,用户可以在其中输入 6 位验证码,每个数字都在单独的 UITextField 中。 UI 是使用 UIStackView 以编程方式创建的,以水平排列文本字段。

但是,我面临退格功能的问题。当光标位于第二个占位符并按下退格键时,光标应移动到第一个占位符并删除其内容。不幸的是它不起作用。这适用于其他文本字段,但不适用于第一个占位符。

代码: 导入 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 = ""
        }
    }
}
  1. 实现了自定义 UITextField 类 (BackspaceTextField) 来处理退格键按下。
  2. 添加了“清除”按钮来重置所有文本字段,但问题仍然存在。

期望: 当光标位于第二个占位符并按下退格键时,光标应移动到第一个占位符并清除其内容。

实际行动: 光标不会移动到第一个占位符并清除其内容。第一个占位符保留其先前的值。

ios swift uitextfield uistackview backspace
1个回答
0
投票

使用

.tag
属性通常是一个坏主意......它可以很有用,但它也可能导致意想不到的问题。

默认情况下,继承自

UIView
的 UI 元素(几乎是每个元素,包括
UITextField
)的默认
.tag
0

来自苹果的文档

讨论

此方法在当前视图及其所有子视图中搜索指定视图。

因此,

superview?.viewWithTag(0)
返回超级视图

如果您想继续使用

.tag
来跟踪文本字段,请更改此行:

textField.tag = i

至:

textField.tag = i + 1

现在您的文本字段标签将为 1 到 6,而不是 0 到 5。


另一种(也许更好)的方法是:

  • 从超级视图的子视图中获取
    BackspaceTextField
    数组
  • 获取
    .firstIndex(of: self)
  • 如果该索引 > 0
    • 上一个文本字段是
      index - 1
  • 否则
    • 您位于第一个文本字段

大致如下:

class BackspaceTextField: UITextField {
    
    override func deleteBackward() {
        
        // in case we moved the caret in front of the entered digit
        if !(text ?? "").isEmpty {
            text = ""
            return
        }
        
        // make sure we have a superview
        guard let sv = superview else {
            text = ""
            return
        }
        
        // get array of superview's subviews that are BackspaceTextField
        let rtfArray = sv.subviews.compactMap{$0 as? BackspaceTextField}
        
        guard !rtfArray.isEmpty else {
            text = ""
            return
        }
        
        // get index of self, and make sure it's not the first text field
        guard let idx = rtfArray.firstIndex(of: self),
              idx > 0
        else {
            text = ""
            return
        }
        
        // clear the text of the previous field and set it as first responder
        rtfArray[idx - 1].text = ""
        rtfArray[idx - 1].becomeFirstResponder()
    }
    
}
© www.soinside.com 2019 - 2024. All rights reserved.