I think I nailed it. I was trying subclassing NSTextFiled
to override becomeFirstResponder()
and resignFirstResponder()
, but once I click it, becomeFirstResponder()
gets called and resignFirstResponder()
gets called right after that. Huh? But search field looks like still under editing and focus is still on it.
I figured out that, when you clicked on search field, search field become first responder once, but NSText
will be prepared sometime somewhere later, and the focus will be moved to the NSText
.
I found out that when NSText
is prepared, it is set to self.currentEditor()
. The problem is that when becomeFirstResponder()
's call, self.currentEditor()
hasn't set yet. So becomeFirstResponder()
is not the method to detect it's focus.
On the other hand, when focus is moved to NSText
, text field's resignFirstResponder()
is called, and you know what? self.currentEditor()
has set. So, this is the moment to tell it's delegate that that text field got focused.
Then next, how to detect when search field lost it's focus. Again, it's about NSText
. Then you need to listen to NSText
delegate's methods like textDidEndEditing()
, and make sure you let it's super class to handle the method and see if self.currentEditor()
is nullified. If it is the case, NSText
lost it's focus and tell text field's delegate about it.
I provide a code, actually NSSearchField
subclass to do the same thing. And the same principle should work for NSTextField
as well.
protocol ZSearchFieldDelegate: NSTextFieldDelegate {
func searchFieldDidBecomeFirstResponder(textField: ZSearchField)
func searchFieldDidResignFirstResponder(textField: ZSearchField)
}
class ZSearchField: NSSearchField, NSTextDelegate {
var expectingCurrentEditor: Bool = false
// When you clicked on serach field, it will get becomeFirstResponder(),
// and preparing NSText and focus will be taken by the NSText.
// Problem is that self.currentEditor() hasn't been ready yet here.
// So we have to wait resignFirstResponder() to get call and make sure
// self.currentEditor() is ready.
override func becomeFirstResponder() -> Bool {
let status = super.becomeFirstResponder()
if let _ = self.delegate as? ZSearchFieldDelegate where status == true {
expectingCurrentEditor = true
}
return status
}
// It is pretty strange to detect search field get focused in resignFirstResponder()
// method. But otherwise, it is hard to tell if self.currentEditor() is available.
// Once self.currentEditor() is there, that means the focus is moved from
// serach feild to NSText. So, tell it's delegate that the search field got focused.
override func resignFirstResponder() -> Bool {
let status = super.resignFirstResponder()
if let delegate = self.delegate as? ZSearchFieldDelegate where status == true {
if let _ = self.currentEditor() where expectingCurrentEditor {
delegate.searchFieldDidBecomeFirstResponder(self)
// currentEditor.delegate = self
}
}
self.expectingCurrentEditor = false
return status
}
// This method detect whether NSText lost it's focus or not. Make sure
// self.currentEditor() is nil, then that means the search field lost its focus,
// and tell it's delegate that the search field lost its focus.
override func textDidEndEditing(notification: NSNotification) {
super.textDidEndEditing(notification)
if let delegate = self.delegate as? ZSearchFieldDelegate {
if self.currentEditor() == nil {
delegate.searchFieldDidResignFirstResponder(self)
}
}
}
}
You will need to change NSSerachField
to ZSearchField
, and your client class must conform to ZSearchFieldDelegate
not NSTextFieldDelegate
. Here is a example. When user clicked on search field, it extend it's width and when you click on the other place, search field lost it's focus and shrink its width, by changing the value of NSLayoutConstraint
set by Interface Builder.
class MyViewController: NSViewController, ZSearchFieldDelegate {
// [snip]
@IBOutlet weak var searchFieldWidthConstraint: NSLayoutConstraint!
func searchFieldDidBecomeFirstResponder(textField: ZSearchField) {
self.searchFieldWidthConstraint.constant = 300
self.view.layoutSubtreeIfNeeded()
}
func searchFieldDidResignFirstResponder(textField: ZSearchField) {
self.searchFieldWidthConstraint.constant = 100
self.view.layoutSubtreeIfNeeded()
}
}
It might depend on the behavior of the OS, I tried on El Capitan 10.11.4, and it worked.
The code can be copied from Gist as well.
https://gist.github.com/codelynx/aa7a41f5fd8069a3cfa2