0

I have a window that presents a popover with a bunch of text fields. I want these text fields to be tabbable but not focused when the popover appears. To achieve this I set the first responder to nil when the popover appears:

// Inside popover's view controller.
override func viewDidAppear() {
    self.view.window!.makeFirstResponder(nil)
}

This works fine up until the point when the popover gets dismissed causing the owning window first responder being set to the window itself, not the view that was the first responder prior the popover appeared. However, if I do self.view.window!.makeFirstResponder(self.view) or don't touch the first responder at all in the above block, everything works as expected and the owning window's first responder gets restored correctly when the popover gets dismissed.

To my knowledge changes inside the popover shouldn't affect the owning window, since popovers have own windows with own responder chains.

I'm very curious what's happening behind the scenes. Pretty sure this gets down to how the responder chain works and gets updated, but I can't connect the dots.

–––

Can anyone explain why changing the first responder inside the popover to nil messes up the owning window's (above which it gets displayed) first responder when the popover gets dismissed? And doesn't affect it when using the aforementioned workaround?

Ian Bytchek
  • 7,964
  • 5
  • 37
  • 67
  • Newly opened popover doesn't have initial key responder and it can't become key. Your code manually forces first responder (key events) to be the window. Once the popover is off screen you need to manually restore first responder (because you set it to nil). – Marek H Feb 16 '19 at 19:03
  • Hmmm. Okay. But why would the owning window care about what's going on inside the popover and its window? As far as I understand responder chains are local to window and once the popover is presented it's on its own. No? – Ian Bytchek Feb 17 '19 at 14:12
  • 2
    Because the popover is displayed in hidden window and underlying window is it's parent. Child window - nothing accepts key events. Not 100% sure of all this but you can dissasemble AppKit using Hopper to look what is going on. Also look at recalculatekeyviewloop – Marek H Feb 17 '19 at 15:19
  • 1
    http://mikeabdullah.net/nspopover-key-view-loop.html and https://stackoverflow.com/questions/34612744/nsresponder-chain-being-broken-after-i-remove-insert-views – Marek H Feb 17 '19 at 15:24
  • Thanks Marek! There are definitely some hints. – Ian Bytchek Feb 20 '19 at 11:06

1 Answers1

2

The popover window is a child window of the owning window and shares the first responder with its parent. When the popover closes _NSPopoverCloseAndAnimate: is called. If the first responder of the popover is a subclass of NSView then _updateFirstResponderForIgnoredChildWindow: is called on the owning window and it will set a first responder. If the first responder of the popover is a window then the first responder of the owning window isn't restored.

If the popover doesn't contain any views which can be first responder then the text field of the owning window stays first responder and accepts keydowns.

Willeke
  • 12,344
  • 3
  • 16
  • 41