Keeping window on top when switching spaces

2.3k Views Asked by At

I have created a window using -[NSWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces].
It only does half of what I want, though: when I switch spaces, the window also switches spaces (as expected), but my window moves to the back behind all other windows in that space. This is especially bad because my app is active but its window is below all other apps' windows. I tried changing the level to NSFloatingWindowLevel, and that does keep it on top, but then it loses key status (focus) when switching spaces.

I tried NSWindowCollectionBehaviorMoveToActiveSpace for the collection behaviour but it's definitely not what I'm looking for.

Is there hope? I know there are almost no other APIs that relate to Spaces.

3

There are 3 best solutions below

1
On BEST ANSWER

Spaces is a pain. My solution was to register for a change notification like so:

[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self 
                             selector:@selector(activeSpaceDidChange:)
                                 name:NSWorkspaceActiveSpaceDidChangeNotification 
                               object:nil];

Then in my WindowController class:

- (void) activeSpaceDidChange:(NSNotification *)aNotification {
    if ([NSApp isActive]) [[self window] orderFront:self];
}
0
On

Swift 5 solution of @francis-mcgrew's answer ⤵︎

// Quick Solution: Place this in the AppDelegate in applicationDidFinishLaunching
NSWorkspace.shared.notificationCenter.addObserver(
    self,
    selector: #selector(notifySpaceChanged),
    name: NSWorkspace.activeSpaceDidChangeNotification,
    object: nil
)

// and this in the same class
@objc func notifySpaceChanged() {
    window.orderFrontRegardless()
}
0
On

For borderless windows (created with NSBorderlessWindowMask), I banged my head until I came up with the following modification to Francis':

- (void) activeSpaceDidChange:(NSNotification *)aNotification {

    if ([NSApp isActive]) 
    {
        NSRect windowRect = [[self window] frame];
        [[self window] setStyleMask:NSTitledWindowMask];
        [[self window] setStyleMask:NSBorderlessWindowMask];
        [[self window] setFrame:windowRect display:YES];
        [[NSApplication sharedApplication] activateIgnoringOtherApps : YES];
    }
}

I saw others stating that borderless windows have issues which led me to the idea to trick it momentarily into not seeing it as a borderless window. First I had setting the style mask to "Titled" followed by "activateIgnoringOtherApps" and then setting the "borderless" style back, which seemed to be a more logical solution. Yet, just to see what minimal solution was required for it to function, I ended up seeing the above works. Be great if somebody could fill in what is exactly happening that allows this to work.