NSTextField keep focus/first responder after NSPopover

Here's my attempt with help from How can one programatically begin a text editing session in a NSTextField? and How can I make my NSTextField NOT highlight its text when the application starts?:

The selected range is saved in textShouldEndEditing and restored in becomeFirstResponder. insertText(_:replacementRange:) starts an editing session.

var savedSelectedRanges: [NSValue]?

override func becomeFirstResponder() -> Bool {
    if super.becomeFirstResponder() {
        if self.aboutToShowPopover {
            if let ranges = self.savedSelectedRanges {
                if let fieldEditor = self.currentEditor() as? NSTextView {
                    fieldEditor.insertText("", replacementRange: NSRange(location: 0, length:0))
                    fieldEditor.selectedRanges = ranges
                }
            }
        }
        return true
    }
    return false
}

override func textShouldEndEditing(_ textObject: NSText) -> Bool {
    if super.textShouldEndEditing(textObject) {
        if self.aboutToShowPopover {
            let fieldEditor = textObject as! NSTextView
            self.savedSelectedRanges = fieldEditor.selectedRanges
            return true
        }
        let s = textObject.string
        if s == "2" {
            return true
        }
    }
    return false
}

Maybe rename aboutToShowPopover.


If you subclass each of your NSTextField, you could override the method becomeFirstResponder and make it send self to a delegate class you will create, that will keep a reference of the current first responder:

NSTextField superclass:

override func becomeFirstResponder() -> Bool {
        self.myRespondersDelegate.setCurrentResponder(self)
        return super.becomeFirstResponder()
    }

(myRespondersDelegate: would optionally be your NSViewController)

Note: do not use the same superclass for your alerts TextFields and ViewController TextFields. Use this superclass with added functionality only for TextFields you would want to return to firstResponder after an alert is closed.

NSTextField delegate:

class MyViewController: NSViewController, MyFirstResponderDelegate {
    var currentFirstResponderTextField: NSTextField?

    func setCurrentResponder(textField: NSTextField) {
        self.currentFirstResponderTextField = textField
    }
}

Now, after your pop is dismissed, you could in viewWillAppear or create a delegate function that will be called on a pop up dismiss didDismisss (Depends how your pop up is implemented, I will show the delegate option) Check If a TextField has existed, and re-make it, the firstResponder.

Pop up delegate:

class MyViewController: NSViewController, MyFirstResponderDelegate, MyPopUpDismissDelegate {
    var currentFirstResponderTextField: NSTextField?

    func setCurrentResponder(textField: NSTextField) {
        self.currentFirstResponderTextField = textField
    }

    func didDismisssPopUp() {
        guard let isLastTextField = self.currentFirstResponderTextField else  {
            return
        }
        self.isLastTextField?.window?.makeFirstResponder(self.isLastTextField)
    }
}

Hope it works.