How to Open a Nspopover at a Distance from the System Bar

How to open a NSPopover at a distance from the system bar?

First, the reason why button.bounds.offsetBy(dx: 0.0, dy: -20.0) didn't work is because those coordinate fell outside the "window" of the status bar item which is the status bar itself. So anything outside of it was cropped.

I solved this problem by collecting information here and there:

  1. Create an invisible window.
  2. Find the coordinates in the screen of the status bar item and position the invisible window under it.
  3. Show the NSPopover in relation to the invisible window and not the status bar item.

Sample Image

The red thing is the invisible window (for demonstration purposes).

Swift 4 (Xcode 9.2)

// Create a window
let invisibleWindow = NSWindow(contentRect: NSMakeRect(0, 0, 20, 5), styleMask: .borderless, backing: .buffered, defer: false)
invisibleWindow.backgroundColor = .red
invisibleWindow.alphaValue = 0

if let button = statusBarItem.button {
// find the coordinates of the statusBarItem in screen space
let buttonRect:NSRect = button.convert(button.bounds, to: nil)
let screenRect:NSRect = button.window!.convertToScreen(buttonRect)

// calculate the bottom center position (10 is the half of the window width)
let posX = screenRect.origin.x + (screenRect.width / 2) - 10
let posY = screenRect.origin.y

// position and show the window
invisibleWindow.setFrameOrigin(NSPoint(x: posX, y: posY))
invisibleWindow.makeKeyAndOrderFront(self)

// position and show the NSPopover
mainPopover.show(relativeTo: invisibleWindow.contentView!.frame, of: invisibleWindow.contentView!, preferredEdge: NSRectEdge.minY)
NSApp.activate(ignoringOtherApps: true)
}

I was trying to use show(relativeTo: invisibleWindow.frame ...) and the popup wasn't showing up because NSWindow is not an NSView. For the popup to be displayed a view has to be passed.

NSPopover and adding spacing with NSStatusItem

To achieve this i added a padding view and attach set NSStatusItem View to that container view. Code from the solution used is as follow for anybody looking to implement it.

_paddingView = [NSView new];
[_containerView addSubview:_paddingView];
[_containerView addSubview:_dragView];

[_dragView setTranslatesAutoresizingMaskIntoConstraints:NO];
[_paddingView setTranslatesAutoresizingMaskIntoConstraints:NO];
[_containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_dragView(22)][_paddingView(5)]|" options:0
metrics:nil views:views]];
[_containerView addConstraint:[NSLayoutConstraint constraintWithItem:_dragView
attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
toItem:_paddingView attribute:NSLayoutAttributeLeft
multiplier:1. constant:0]];
[_containerView addConstraint:[NSLayoutConstraint constraintWithItem:_dragView
attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual
toItem:_paddingView attribute:NSLayoutAttributeRight
multiplier:1. constant:0]];

self.mainPopover = [[NSPopover alloc] init];
self.mainPopover.delegate = self;
self.mainPopover.backgroundColor = [NSColor greenColor];
[self.mainPopover setAnimates:NO];
[self.mainPopover setBehavior:NSPopoverBehaviorTransient];
[self.mainPopover setContentViewController:viewController];

[_containerView layoutSubtreeIfNeeded];

[_statusItem setView:_containerView];

Show NSPopover without Focus

This is a workaround, not exactly how I was trying to accomplish this, but it works. I save the cursor position in the textfield prior to opening the popover, then give the textfield first responder and change the cursor position back to where it was.

if ([_commandBar stringValue].length > 0){
NSString *command = [_commandBar stringValue];
NSRange range = [[_commandBar currentEditor] selectedRange];

//Open popover if command is being typed
if ([[command substringFromIndex: command.length - 1] isEqualToString: @"#"]){
CompletionMenuController *completionController = [[CompletionMenuController alloc] initWithNibName: @"CompletionMenuController" bundle:[NSBundle mainBundle]];

//Configure and Open Popover
if ([completionMenuPopover isShown]) [completionMenuPopover close];
completionMenuPopover = [[NSPopover alloc] init];
[completionMenuPopover setContentViewController: completionController];
[completionMenuPopover setContentSize: completionController.view.frame.size];
[completionMenuPopover setBehavior: NSPopoverBehaviorTransient];
[completionMenuPopover setAppearance: NSPopoverAppearanceHUD];
[completionMenuPopover setAnimates: NO];
[completionMenuPopover showRelativeToRect:[_commandBar frame] ofView:_commandBar preferredEdge:NSMaxYEdge];

//Reset Command Bar as First Responder
[_commandBar becomeFirstResponder];
[[_commandBar currentEditor] setSelectedRange: range];
}
}

NSPopover Not Appearing

Turns out the coordinate system was wrong:

NSRect theRect = [[NSApp keyWindow] convertRectFromScreen: NSMakeRect(700, 400, 5, 5)];
[tagPopover showRelativeToRect: theRect // Window Coordinates
ofView: [[NSApp keyWindow] contentView]
preferredEdge: NSMinYEdge];

Needed to convert it to the window's coordinate system first.



Related Topics



Leave a reply



Submit