Force NSPopover to not become first responder with NSTokenField

て烟熏妆下的殇ゞ 提交于 2019-12-21 02:34:49

问题


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 first responder. Is there a way to show the popover but not to loose the first responder on the NSTokenField?

Overriding NSTokenField's resignFirstResponder causes the NSTokenField to stop working (it doesn't accept any keystrokes). Overriding NSPopover's acceptsFirstResponder method or setting it's behaviour as suggested in this question does not work either.

Edit: The NSTokenField is not inside the NSPopover's contentViewController.view. Edit2: The behaviour like the search bar in the Mailapp would be to optimal solution.


回答1:


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.




回答2:


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.




回答3:


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)
    }
}



回答4:


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.



来源:https://stackoverflow.com/questions/21110622/force-nspopover-to-not-become-first-responder-with-nstokenfield

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!