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?
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.