iosswiftuikituitextfielddidset

UITextField in Swift – text didSet not called when typing


I am writing a custom UITextField right now in Swift, and encountered the following:

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) {
    }
}

Now, when testing this, I observed that, when the user is typing something, textEditingChanged is called, but text.didSet is not. Neither is text.willSet, for that matter. However, in textEditingChanged, the textfield's text will already be updated to the new value.

Can anyone explain what is going on here? Is Swift circumventing the setter on purpose? How am I supposed to know when/if the setter is called, is there any logic to this in UIKit?


Solution

  • The text property of the UITextField is only for external use, when you are typing UIKit handles the update internally. Can't really tell whats going on under the hood, as we do not have access to UIKit implementation but one possible answer to the question is that UITextField is using an other internal variable for storing the text property. When you are getting the text property, you are actually getting that internal value, and when you are setting it, you are setting that internal value also. Of course under the hood it is a bit more complicated but it may look something like this(SampleUITextField represents the 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
        }
    }
    

    And when you subclass it it looks like:

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

    Now when you set the text property directly the didSet is called because the text value updates. You can check it:

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

    But now when you call updateInternalTextWith the didSet is not called:

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

    That's because you are not updating the text value directly, but just the internal text property. A similar method is called to update the internal text variable of the UITextField when you are typing, and that's the reason the didSet is not called in your case.

    For that reason, it is not enough to override the text variable when we want to be notified when the text properties changes, and we need to use delegate(UITextFieldDelegate) methods or notifications (textFieldDidChange) to catch the actual change.