Force NSPopover to not become first responder with NSTokenField

后端 未结 4 1541
天命终不由人
天命终不由人 2021-02-09 23:59

I\'m having a NSTokenField and I\'m providing a custom autocompletion inside the popover. Unfortunately as soon as the popover is shown it forces the NSTokenField to resign firs

相关标签:
4条回答
  • 2021-02-10 00:33

    I happened to stumble upon this problem while designing a custom control to be used within a table view.

    It seems that at least in recent versions of Mac OS X, you can send the table inside your Popover (or whatever focus-stealing view you've got there) a tableView.refusesFirstResponder = true.

    The popover will no longer attempt to steal first responder if you do this.

    0 讨论(0)
  • 2021-02-10 00:34

    I adapted Will's answer for Swift:

    protocol PopoverFirstResponderStealingSuppression {
        var suppressFirstResponderWhenPopoverShows: Bool { get }
    }
    
    class TTWindow: NSWindow {
        override func makeFirstResponder(_ responder: NSResponder?) -> Bool {
            if responder != firstResponder, let responderView = responder as? NSView {
                // Prevent popover content view from forcing our current first responder to resign
    
                let newFirstResponderWindow = responderView.window!
                var currentFirstResponderWindow: NSWindow? = nil
    
                let currentFirstResponder = firstResponder
                if let currentFirstResponder = currentFirstResponder as? NSWindow {
                    currentFirstResponderWindow = currentFirstResponder
                }
                else if let currentFirstResponder = currentFirstResponder as? NSView {
                    currentFirstResponderWindow = currentFirstResponder.window
                }
    
                // Prevent some view in popover from stealing our first responder, but allow the user to explicitly activate it with a click on the popover.
                // Note that the current first responder may be in a child window, if it's a control in the "thick titlebar" area and we're currently full-screen.
    
                if newFirstResponderWindow != self, newFirstResponderWindow != currentFirstResponderWindow, currentEvent?.window != newFirstResponderWindow {
    
                    var currentView: NSView? = responderView
                    while currentView != nil {
                        if let currentView = currentView as? PopoverFirstResponderStealingSuppression, currentView.suppressFirstResponderWhenPopoverShows {
                            return false
                        }
    
                        currentView = currentView?.superview
                    }
                }
            }
    
            return super.makeFirstResponder(responder)
        }
    }
    
    0 讨论(0)
  • 2021-02-10 00:39

    Make a subclass of your popover content (a text view?) and implement -(void)canBecomeKeyView. Return NO there. It's called only once when the popover is shown, so you can still interact with it, but it doesn't steal the first responder status anymore.

    0 讨论(0)
  • 2021-02-10 00:45

    Unfortunately, there’s no clean way to do this. Luckily, though, I’ve done it the ugly way in Delicious Library 3—you need to put this method in a subclass of NSWindow, and make sure the document window in question is that subclass:

    - (BOOL)makeFirstResponder:(NSResponder *)responder;
    {
        // Prevent popover content view from forcing our current first responder to resign
        if (responder != self.firstResponder && [responder isKindOfClass:[NSView class]]) {
            NSWindow *const newFirstResponderWindow = ((NSView *)responder).window;
            NSWindow *currentFirstResponderWindow;
    
            NSResponder *const currentFirstResponder = self.firstResponder;
            if ([currentFirstResponder isKindOfClass:[NSWindow class]])
                currentFirstResponderWindow = (id)currentFirstResponder;
            else if ([currentFirstResponder isKindOfClass:[NSView class]])
                currentFirstResponderWindow = ((NSView *)currentFirstResponder).window;
    
            // Prevent some view in popover from stealing our first responder, but allow the user to explicitly activate it with a click on the popover.
            // Note that the current first responder may be in a child window, if it's a control in the "thick titlebar" area and we're currently full-screen.
            if (newFirstResponderWindow != self && newFirstResponderWindow != currentFirstResponderWindow && self.currentEvent.window != newFirstResponderWindow)
                for (NSView *responderView = (id)responder; responderView; responderView = responderView.superview)
                    if ([responderView conformsToProtocol:@protocol(LIPopoverFirstResponderStealingSuppression)] &&
                        ((id <LIPopoverFirstResponderStealingSuppression>)responderView).suppressFirstResponderWhenPopoverShows)
                        return NO;
        }
    
        return [super makeFirstResponder:responder];
    }
    

    Now make sure the popover’s content view subclass implements this protocol:

    // NSPopover doesn't respect -acceptsFirstResponder of its content view (Radar 10666891).
    @protocol LIPopoverFirstResponderStealingSuppression <NSObject>
    @property (readonly, nonatomic) BOOL suppressFirstResponderWhenPopoverShows;
    @end
    

    Please also file a bug with Apple to request NSPopover respect -acceptsFirstResponder of its content view; it is 100% the case that when multiple developers file bugs they get fixed.

    0 讨论(0)
提交回复
热议问题