如何在 iOS 中触发 textField(_:shouldChangeCharactersIn:replacementString:) 方法

How to trigger textField(_:shouldChangeCharactersIn:replacementString:) method in iOS

我正在尝试在 UITextField 上制作一些功能,用于输入数量

包装器视图包含此 UITextField,并且该视图符合 UITextFieldDelegate

所以我在 Wrapper 视图中实现 textField(_:shouldChangeCharactersIn:replacementString:) 方法 class

在那个方法中,我从键盘操作输入文本(当数量超过最大输入数量时添加逗号或块输入)

它在系统键盘上运行良好,但在自定义键盘上运行不佳

在自定义键盘中,我制作按钮,如果按下某个按钮,我将文本设置为我制作的文本字段(数量文本字段),如下所示

textField.text = someText

但是将文本显式设置为文本字段,它不会触发 textField(_:shouldChangeCharactersIn:replacementString:) 方法

如何在使用自定义键盘时触发 textField(_:shouldChangeCharactersIn:replacementString:) 方法

你不能“触发”shouldChangeCharactersIn,但有多种方法可以解决这个问题。

一种方法是将您的“验证”代码移动到它自己的函数中。然后,您可以从 shouldChangeCharactersIn 和自定义键盘输入中调用该函数。

所以,对于一个非常简单的例子——只允许输入数字——它可能看起来像这样:

func myShouldChangeCharacters(_ textField: UITextField, in range: NSRange, replacementString string: String) -> Bool {

    // whatever you want to do to validate the input

    if !"0123456789".contains(string) {
        return false
    }
    return true
}

// textField delegate call
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    return myShouldChangeCharacters(textField, in: range, replacementString: string)
}

// button tapped    
@objc func btnTapped(_ sender: Any) {
    // make sure we're
    //  called by a button
    //  get the button title
    //  convert textField's .selectedTextRange to a NSRange
    guard let btn = sender as? UIButton,
          let str = btn.currentTitle,
          let rng = myTextField.selectedRange
    else {
        return
    }
    if myShouldChangeCharacters(myTextField, in: rng, replacementString: str) {
        myTextField.insertText(str)
    }
}

使用此扩展名:

// helper extension to convert
//  text field's selectedTextRange
//  to a NSRange
extension UITextInput {
    var selectedRange: NSRange? {
        guard let range = selectedTextRange else { return nil }
        let location = offset(from: beginningOfDocument, to: range.start)
        let length = offset(from: range.start, to: range.end)
        return NSRange(location: location, length: length)
    }
}

这是一个完整的示例,顶部有一个文本字段,下面有 4 个按钮,分别标记为 1、2、A、B,并且只允许使用数字:

class ViewController: UIViewController, UITextFieldDelegate {

    let myTextField: UITextField = {
        let v = UITextField()
        v.borderStyle = .roundedRect
        return v
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let btnsStack: UIStackView = {
            let v = UIStackView()
            v.axis = .horizontal
            v.distribution = .fillEqually
            v.spacing = 40.0
            return v
        }()
        
        // create 4 buttons
        ["1", "2", "A", "B"].forEach { str in
            let b = UIButton()
            b.setTitle(str, for: [])
            b.setTitleColor(.white, for: .normal)
            b.setTitleColor(.gray, for: .highlighted)
            b.backgroundColor = .systemRed
            b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
            btnsStack.addArrangedSubview(b)
        }
        
        [myTextField, btnsStack].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
        }
        
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            myTextField.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            myTextField.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            myTextField.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            
            btnsStack.topAnchor.constraint(equalTo: myTextField.bottomAnchor, constant: 20.0),
            btnsStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            btnsStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),

        ])
        
        myTextField.delegate = self
    }
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        return myShouldChangeCharacters(textField, in: range, replacementString: string)
    }
    
    @objc func btnTapped(_ sender: Any) {
        // make sure we're
        //  called by a button
        //  get the button title
        //  convert textField's .selectedTextRange to a NSRange
        guard let btn = sender as? UIButton,
              let str = btn.currentTitle,
              let rng = myTextField.selectedRange
        else {
            return
        }
        if myShouldChangeCharacters(myTextField, in: rng, replacementString: str) {
            myTextField.insertText(str)
        }
    }
    
    func myShouldChangeCharacters(_ textField: UITextField, in range: NSRange, replacementString string: String) -> Bool {
        if !"0123456789".contains(string) {
            return false
        }
        return true
    }
    
}

// helper extension to convert
//  text field's selectedTextRange
//  to a NSRange
extension UITextInput {
    var selectedRange: NSRange? {
        guard let range = selectedTextRange else { return nil }
        let location = offset(from: beginningOfDocument, to: range.start)
        let length = offset(from: range.start, to: range.end)
        return NSRange(location: location, length: length)
    }
}