Swift 中的 UITextField – 键入时不调用文本 didSet

UITextField in Swift – text didSet not called when typing

我现在正在 Swift 中编写自定义 UITextField,遇到以下情况:

class MyField: UITextField {
    
    open override var text: String? {
        didSet {
            // some logic that normalizes the text
        }
    }

    private func setup() { //called on init
        addTarget(self, action: #selector(textEditingChanged(_:)), for: .editingChanged)
    }

    @objc func textEditingChanged(_ sender: UITextField) {
    }
}

现在,在对此进行测试时,我观察到,当用户输入内容时,会调用 textEditingChanged,但不会调用 text.didSet。就此而言,text.willSet 也不是。但是,在 textEditingChanged 中,文本字段的文本将已更新为新值。

谁能解释一下这是怎么回事? Swift 是故意绕过 setter 吗?我怎么知道 when/if setter 被调用了,在 UIKit 中有什么逻辑吗?

您要在 UITextField 上执行哪种格式设置?

难道不应该由 ViewModel(或管理包含此文本字段的视图的业务逻辑的任何模型)负责吗?

此外,您不应该使用 didSet 来执行更改,它主要用于允许您响应更改,而不是链接另一个更改。

UITextField 的文本属性 仅供外部使用,当您输入时UIKit 会在内部处理更新。无法真正知道幕后发生了什么,因为我们无法访问 UIKit 实现,但问题的一个可能答案是 UITextField 正在使用另一个内部变量来存储文本 属性。当您获取文本 属性 时,您实际上是在获取该内部值,而当您设置它时,您也在设置该内部值。当然在引擎盖下它有点复杂,但它可能看起来像这样(SampleUITextField 代表 UITextField):

// This how the UITextField implementation may look like
class SampleUITextField {
    private var internalText: String = ""

    var text: String  {
        get {
            internalText
        } set {
            internalText = newValue
        }
    }

    // This method is just to demonstrate that when you update the internalText didSet is not called 
    func updateInternalTextWith(text: String) {
       internalText = text
    }
}

当你对其进行子类化时,它看起来像:

class YourTextField: SampleUITextField {
    override var text: String {
        didSet {
            print("DidSet called")
        }
    }
}

现在,当您直接设置文本 属性 时,会调用 didSet,因为 text 值会更新。您可以查看:

let someClass = YourTextField()
someClass.text = "Some new text"
// didSet is called 

但是现在当你调用 updateInternalTextWithdidSet 没有被调用:

let someClass = YourTextField()
someClass.updateInternalTextWith(text: "new text")
// didSet is not called 

那是因为您没有直接更新文本值,而只是更新内部文本 属性。当您键入时,将调用类似的方法来更新 UITextField 的内部文本变量,这就是在您的情况下未调用 didSet 的原因。

因此,当我们想要在文本属性更改时得到通知时,仅覆盖 text 变量是不够的,我们需要使用 delegate(UITextFieldDelegate) 方法或通知(textFieldDidChange) 捕捉实际变化。