NSPopover to start in a detached state
Asked Answered
M

2

9

Is there a way to force the NSPopover to start in the detached state? I only see isDetached which is a read-only property for the state of the popover and an NSPopoverDelegate method detachableWindow(forPopover:) which lets me override the window that gets created. I'd like to essentially click a button and have the NSPopover start in the state in this photo.

The style of this window is exactly what a product requirement is and I can't seem to find any NSWindow style settings that would make a window do something like this (nor an NSPanel)

This detached popover functionality seems special in that it:

  1. non-modal, but stays above main app. Able to still interact with the main app just like in Messages how you can still click around and type a new message.
  2. Clicking another app, AppFoo, puts both the main app and the helper window behind AppFoo.
  3. The helper window can be moved around and isn't hidden on app deactivation (another app gets selected).
  4. Has the little, native, grey X in the top left.

Detached NSPopover from Details button in Messages

Menashem answered 25/6, 2018 at 21:0 Comment(0)
S
3

Here is the trick. Use the required delegate method detachableWindowForPopover: to do the work for you, like:

- (void) showPopoverDetached
{
    NSWindow* detachedWindow = [self detachableWindowForPopover:nil];

    [detachedWindow.windowController showWindow:nil];
}

Seems that the Apple engineers implemented detachableWindowForPopover: on a pretty smart way, I guess it uses the content view controller class, and will always create a singleton like instance of the detached window. Once detachableWindowForPopover: has called the presented window instance will be re-used no matter when and why it is called, called it directly (from a func like my sample above) or indirectly (e.g. when you drag out, detach, the popover from its original position)

This way they can prevent a popover from being detached 'twice' and we can also implement the detached way programmatically, nice job from them!

Here is a tiny demo of how it works in a real life (tested on macOS 10.13 - 13.0)

https://i.sstatic.net/hej75.jpg

Secretion answered 3/12, 2018 at 8:1 Comment(3)
I'm fairly new to mac dev and I don't quite understand the solution. I have implemented popoverShouldDetach to return true in my NSPopoverDelegate, and I can drag my popover to detach it from the chosen NSView it is originally attached to (using popover.show(relativeTo ...). However calling detachableWindowForPopover directly means I need to implement it in my popover delegate right? Currently it is implemented simply as return nil.Maffei
form the help of detachableWindowForPopover: "If there is no delegate, the delegate does not implement this method, or the delegate returns nil, the popup will not be displayed detached." so, yes, you have to implement the delegate method, it will be called when the user drags the popover out from its initial "state" and also you can call it any time to show it in a detached state initiallySecretion
i couldn't get the suggestion to work either. all it will do is pop up an additional window that has no connection to the popover. tested on 10.14Scoundrelly
C
6

If you don't mind calling private API, it's actually pretty simple:

let detach = NSSelectorFromString("detach")
if popover.responds(to: detach) {
    popover.perform(detach)
}

No need to even add a delegate. I don't know when this private method was added but it's available at least since macOS 10.13. I suspect it's available since the introduction of NSPopover, though.

Console answered 8/4, 2020 at 9:57 Comment(0)
S
3

Here is the trick. Use the required delegate method detachableWindowForPopover: to do the work for you, like:

- (void) showPopoverDetached
{
    NSWindow* detachedWindow = [self detachableWindowForPopover:nil];

    [detachedWindow.windowController showWindow:nil];
}

Seems that the Apple engineers implemented detachableWindowForPopover: on a pretty smart way, I guess it uses the content view controller class, and will always create a singleton like instance of the detached window. Once detachableWindowForPopover: has called the presented window instance will be re-used no matter when and why it is called, called it directly (from a func like my sample above) or indirectly (e.g. when you drag out, detach, the popover from its original position)

This way they can prevent a popover from being detached 'twice' and we can also implement the detached way programmatically, nice job from them!

Here is a tiny demo of how it works in a real life (tested on macOS 10.13 - 13.0)

https://i.sstatic.net/hej75.jpg

Secretion answered 3/12, 2018 at 8:1 Comment(3)
I'm fairly new to mac dev and I don't quite understand the solution. I have implemented popoverShouldDetach to return true in my NSPopoverDelegate, and I can drag my popover to detach it from the chosen NSView it is originally attached to (using popover.show(relativeTo ...). However calling detachableWindowForPopover directly means I need to implement it in my popover delegate right? Currently it is implemented simply as return nil.Maffei
form the help of detachableWindowForPopover: "If there is no delegate, the delegate does not implement this method, or the delegate returns nil, the popup will not be displayed detached." so, yes, you have to implement the delegate method, it will be called when the user drags the popover out from its initial "state" and also you can call it any time to show it in a detached state initiallySecretion
i couldn't get the suggestion to work either. all it will do is pop up an additional window that has no connection to the popover. tested on 10.14Scoundrelly

© 2022 - 2024 — McMap. All rights reserved.