Nspopover to Start in a Detached State

NSPopover to start in a detached state

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://imgur.com/a/sfc7e6d

How to make NSPopover 's detached window become modal window

I kind of found the solution myself. It looks working fine now.

To do this, after the detached window ordered to front and become key window, the following code will make it a modal window (where currModalSession is an iVar defined by myself).

- (void)windowDidBecomeKey:(NSNotification *)notification {
if (notification.object == detachedWindow) {
if (!detachedWindow.isModalPanel) {
currModalSession = [NSApp beginModalSessionForWindow:detachedWindow];
[NSApp runModalSession:currModalSession];
}
}
}

Also, you have to end each Modal Session you have opened. So the following code does the job:

- (void)windowWillClose:(NSNotification *)notification {
if (notification.object == detachedWindow) {
if (currModalSession) {
[NSApp endModalSession:currModalSession];
}
}
}

Note: you have to use Modal Session here rather than runModalForWindow for two reasons:

  1. otherwise the main window won't be blocked right away. I don't quite get the reason yet. One possible explanation is: runModalForWindow will not just block user interactions but also internal communications, so main window might need more time to be ready.
  2. if you plan to run another framework modal dialog (e.g. NSOpenPanel) from the detached window, when return, the detached window will become key window before the new modal dialog close, namely runModalForWindow will freeze another to be closed window. That means to be closed window won't be closed.

After detaching an NSPopover the next appearance is missing callout

I think creating a new popover each time is the way to go. It's a cheap operation since the content of the popover is not recreated everytime, just the popover itself.

Getting access to NSPopover window to set level?

NSPopover is a subclass of NSObject, not NSWindow, so setting the window level is not officially supported.

You could try getting the window like this:

NSWindow* popoverWindow = yourPopover.contentViewController.view.window;

I have no idea whether you can then successfully set the level though.

NSStatusItem with NSPopover and NSTextField

Yes, just add the NSWindow+canBecomeKeyWindow (.h and .m) files to your project, and it should work. I'm using this technique in an app I'm currently developing, and it works fine. Make sure NSWindow+canBecomeKeyWindow.m is listed under "Compile Sources" in your project's Build Phases.

As an aside, I'm having other issues using NSPopover to show a window from an NSStatusItem. I haven't actually tried using it in my project, but this looks promising as an alternative.



Related Topics



Leave a reply



Submit