{"title":"Menu Bar App Windows That Open Behind Everything","html_url":"https://deallocatedobjects.com/posts/menu-bar-app-windows-that-open-behind-everything","source_url":"","markdown_content":"Most of my Mac apps live in the menu bar. [Shake It On](https://shakeiton.app), [Multimeter](https://multimeterapp.com) and [Refrain](https://refrainformac.com) all set `LSUIElement` to `YES` in their Info.plist, so there's no Dock icon and no app menu, only a status item. Each one still has a Settings window and an About window, and the first time you wire one up, it's easy to end up with a window that opens behind whatever app you were using.\n\nHere's why that happens and the handful of calls that fix it.\n\n### Why The Window Ends Up Behind\n\n`LSUIElement` gives your app the accessory activation policy. AppKit's header describes it like this:\n\n> The application does not appear in the Dock and does not have a menu bar, but it may be activated programmatically or by clicking on one of its windows.\n\nClicking your status item's menu doesn't activate the app. The app you were in stays active, and its windows stay in front. So when the Settings menu item's action runs `window.makeKeyAndOrderFront(nil)`, the window comes up, but the active app's windows stay above it.\n\nI checked this with a tiny `LSUIElement` test app on macOS 27. After `makeKeyAndOrderFront(nil)`, the window was visible, `isKeyWindow` was `false`, `NSApp.isActive` was `false` and `NSApp.keyWindow` was `nil`. Apple's documentation for `activate(ignoringOtherApps:)` covers the missing piece: `makeKeyAndOrderFront` makes the window key \"when the app is active.\" Until your app activates, nothing you type goes to that window.\n\n### Activating The App\n\nThe fix is to activate the app as well as ordering the window. Multimeter does both in one helper that every window goes through:\n\n```swift title=\"MultimeterApp.swift\"\n/// LSUIElement apps aren't active when a status menu item fires, so the\n/// window has to be raised and the app activated or it opens behind\n/// whatever you were using.\nprivate func show(_ window: NSWindow) {\n  NSApp.activate(ignoringOtherApps: true)\n  window.makeKeyAndOrderFront(nil)\n}\n```\n\nMultimeter does the same before an alert. It calls `NSApp.activate(ignoringOtherApps: true)` before `runModal()` on the `NSAlert` it shows after importing a meter.\n\nActivation also isn't instant. The header for `NSApp.activate()` says \"You shouldn't assume the app will be active immediately after sending this message\" and that the framework doesn't guarantee it'll activate at all. Shake It On and Refrain cover that gap by lifting the window above normal windows for a moment:\n\n```swift title=\"WindowManager.swift\"\n/// Menu bar apps don't own the foreground, so lift the window above\n/// everything briefly or it opens behind the frontmost app.\nprivate func present(_ window: NSWindow) {\n    NSApp.activate(ignoringOtherApps: true)\n    window.level = .floating\n    window.makeKeyAndOrderFront(nil)\n    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {\n        window.level = .normal\n    }\n}\n```\n\nA `.floating` window sits above every app's `.normal` windows, so it's visible right away even if activation lands a beat later. After 0.3 seconds it drops back to `.normal` so it behaves like any other window once you click away.\n\n### activate(ignoringOtherApps:) And macOS 14\n\nmacOS 14 added `NSApp.activate()` with no arguments, along with `yieldActivation(to:)` and `yieldActivation(toApplicationWithBundleIdentifier:)`. This is what Apple calls cooperative activation: the app that's active hands activation to another app, and that app then asks for it with `activate()`. In the SDK the old call is marked `API_TO_BE_DEPRECATED` with the message \"This method will be deprecated in a future release. Use NSApp.activate instead.\"\n\n`API_TO_BE_DEPRECATED` doesn't produce a warning today. I type-checked `NSApp.activate(ignoringOtherApps: true)` with deployment targets of macOS 13, 14 and 26, and none of them warned. The `NSRunningApplication` flag is a different story. `activate(options: .activateIgnoringOtherApps)` warns on a macOS 14 or later target, and its header says \"ignoringOtherApps is deprecated in macOS 14 and will have no effect.\"\n\n`NSApp.activate()` is only available on macOS 14, so it fails to compile in an app that targets 13 without an availability check. Shake It On and Refrain target macOS 14 and could call `activate()` directly. Multimeter still supports macOS 13, so moving it over takes a check:\n\n```swift\nextension NSApplication {\n    func activateForWindow() {\n        if #available(macOS 14, *) {\n            activate()\n        } else {\n            activate(ignoringOtherApps: true)\n        }\n    }\n}\n```\n\n### makeKeyAndOrderFront Vs orderFrontRegardless\n\n`orderFrontRegardless()` looks like it was made for this. Apple's documentation says it moves the window \"to the front of its level, even if its application isn't active, without changing either the key window or the main window.\" That last part is the catch for a Settings window. In my test app, after `orderFrontRegardless()` the window was visible, it wasn't key and the app wasn't active, so typing still went to the other app.\n\nUse `orderFrontRegardless()` for a panel you only need to see, such as a HUD or a status overlay. For a window with text fields or keyboard shortcuts, activate the app and use `makeKeyAndOrderFront(nil)`.\n\n### SwiftUI's Settings Scene\n\nSwiftUI has two ways to open the `Settings` scene from code, and both need macOS 14: `SettingsLink`, a view you put in a menu or window, and the `openSettings` environment action. The older trick of sending `showSettingsWindow:` up the responder chain doesn't open it. In my `LSUIElement` test app on macOS 27, `NSApp.sendAction(Selector((\"showSettingsWindow:\")), to: nil, from: nil)` returned `true`, and no window appeared.\n\n`openSettings` does open the window, but it doesn't activate the app. In the test, calling it from a SwiftUI view in an `LSUIElement` app opened the Settings window with `isKeyWindow` `false` and `NSApp.isActive` `false`. So you still call `NSApp.activate()` yourself, before or right after.\n\nThe other problem is reaching `openSettings` at all. It's an environment value, so only a SwiftUI view can read it. Multimeter builds its menu with `NSStatusItem` and `NSMenu`, which aren't views. Shake It On and Refrain use `MenuBarExtra`, where `SettingsLink` would fit, but all three host their SwiftUI settings view in an `NSWindow` they own and show it with an activate-and-raise helper like the ones above:\n\n```swift\nlet window = NSWindow(\n    contentRect: NSRect(x: 0, y: 0, width: 480, height: 640),\n    styleMask: [.titled, .closable, .miniaturizable],\n    backing: .buffered,\n    defer: false\n)\nwindow.title = \"Settings\"\nwindow.contentView = NSHostingView(rootView: SettingsView())\nwindow.isReleasedWhenClosed = false\nwindow.center()\n```\n\nMultimeter's `App` body still declares `Settings { EmptyView() }`, because a SwiftUI `App` needs at least one scene. Owning the window also means keeping a reference to it, so the second click on Settings brings back the same window instead of opening another.\n\n### Borrowing A Dock Icon While A Window Is Open\n\nYou can also change the activation policy at runtime. `setActivationPolicy(_:)` has accepted any policy since OS X 10.9 and returns whether it worked. Switching to `.regular` while Settings is open gives the app a Dock icon and its own menu bar, like any other app with a window up. Switch back when the window closes:\n\n```swift\nfunc showSettings() {\n    NSApp.setActivationPolicy(.regular)\n    NSApp.activate()\n    settingsWindow.makeKeyAndOrderFront(nil)\n}\n\n// NSWindowDelegate\nfunc windowWillClose(_ notification: Notification) {\n    NSApp.setActivationPolicy(.accessory)\n}\n```\n\nIf the app can have more than one window open, only switch back when the last one closes, or the Dock icon disappears while a window is still up. In an app where I let people turn the Dock icon on and off, setting `.accessory` is followed by `NSApp.activate(ignoringOtherApps: true)` on the next run loop pass, so an open Settings window stays in front.\n\nNone of our shipping apps flip the policy for Settings. Activating the app plus `makeKeyAndOrderFront(nil)` covers it, and on a macOS 14 target that's `NSApp.activate()` followed by `window.makeKeyAndOrderFront(nil)` in the menu item's action.","html_content":"<p>Most of my Mac apps live in the menu bar. <a href=\"https://shakeiton.app\">Shake It On</a>, <a href=\"https://multimeterapp.com\">Multimeter</a> and <a href=\"https://refrainformac.com\">Refrain</a> all set <code>LSUIElement</code> to <code>YES</code> in their Info.plist, so there’s no Dock icon and no app menu, only a status item. Each one still has a Settings window and an About window, and the first time you wire one up, it’s easy to end up with a window that opens behind whatever app you were using.</p>\n<p>Here’s why that happens and the handful of calls that fix it.</p>\n<h2 id=\"why-the-window-ends-up-behind\" data-level=\"3\">Why The Window Ends Up Behind</h2>\n<p><code>LSUIElement</code> gives your app the accessory activation policy. AppKit’s header describes it like this:</p>\n<blockquote>\n<p>The application does not appear in the Dock and does not have a menu bar, but it may be activated programmatically or by clicking on one of its windows.</p>\n</blockquote>\n<p>Clicking your status item’s menu doesn’t activate the app. The app you were in stays active, and its windows stay in front. So when the Settings menu item’s action runs <code>window.makeKeyAndOrderFront(nil)</code>, the window comes up, but the active app’s windows stay above it.</p>\n<p>I checked this with a tiny <code>LSUIElement</code> test app on macOS 27. After <code>makeKeyAndOrderFront(nil)</code>, the window was visible, <code>isKeyWindow</code> was <code>false</code>, <code>NSApp.isActive</code> was <code>false</code> and <code>NSApp.keyWindow</code> was <code>nil</code>. Apple’s documentation for <code>activate(ignoringOtherApps:)</code> covers the missing piece: <code>makeKeyAndOrderFront</code> makes the window key “when the app is active.” Until your app activates, nothing you type goes to that window.</p>\n<h2 id=\"activating-the-app\" data-level=\"3\">Activating The App</h2>\n<p>The fix is to activate the app as well as ordering the window. Multimeter does both in one helper that every window goes through:</p>\n<figure class=\"code\" data-lang=\"swift\"><figcaption><span class=\"file\">MultimeterApp.swift</span><span class=\"lang\">Swift</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">/// LSUIElement apps aren't active when a status menu item fires, so the</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">/// window has to be raised and the app activated or it opens behind</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">/// whatever you were using.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">private</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> func</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> show</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> window: NSWindow) {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  NSApp.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">activate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">ignoringOtherApps</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  window.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">makeKeyAndOrderFront</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">nil</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre></figure><p>Multimeter does the same before an alert. It calls <code>NSApp.activate(ignoringOtherApps: true)</code> before <code>runModal()</code> on the <code>NSAlert</code> it shows after importing a meter.</p>\n<p>Activation also isn’t instant. The header for <code>NSApp.activate()</code> says “You shouldn’t assume the app will be active immediately after sending this message” and that the framework doesn’t guarantee it’ll activate at all. Shake It On and Refrain cover that gap by lifting the window above normal windows for a moment:</p>\n<figure class=\"code\" data-lang=\"swift\"><figcaption><span class=\"file\">WindowManager.swift</span><span class=\"lang\">Swift</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">/// Menu bar apps don't own the foreground, so lift the window above</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">/// everything briefly or it opens behind the frontmost app.</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">private</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> func</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> present</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> window: NSWindow) {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    NSApp.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">activate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">ignoringOtherApps</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    window.level </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> .floating</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    window.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">makeKeyAndOrderFront</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">nil</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    DispatchQueue.main.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">asyncAfter</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">deadline</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: .</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">now</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">() </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">+</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0.3</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">        window.level </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> .normal</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre></figure><p>A <code>.floating</code> window sits above every app’s <code>.normal</code> windows, so it’s visible right away even if activation lands a beat later. After 0.3 seconds it drops back to <code>.normal</code> so it behaves like any other window once you click away.</p>\n<h2 id=\"activate-ignoringotherapps-and-macos-14\" data-level=\"3\">activate(ignoringOtherApps:) And macOS 14</h2>\n<p>macOS 14 added <code>NSApp.activate()</code> with no arguments, along with <code>yieldActivation(to:)</code> and <code>yieldActivation(toApplicationWithBundleIdentifier:)</code>. This is what Apple calls cooperative activation: the app that’s active hands activation to another app, and that app then asks for it with <code>activate()</code>. In the SDK the old call is marked <code>API_TO_BE_DEPRECATED</code> with the message “This method will be deprecated in a future release. Use NSApp.activate instead.”</p>\n<p><code>API_TO_BE_DEPRECATED</code> doesn’t produce a warning today. I type-checked <code>NSApp.activate(ignoringOtherApps: true)</code> with deployment targets of macOS 13, 14 and 26, and none of them warned. The <code>NSRunningApplication</code> flag is a different story. <code>activate(options: .activateIgnoringOtherApps)</code> warns on a macOS 14 or later target, and its header says “ignoringOtherApps is deprecated in macOS 14 and will have no effect.”</p>\n<p><code>NSApp.activate()</code> is only available on macOS 14, so it fails to compile in an app that targets 13 without an availability check. Shake It On and Refrain target macOS 14 and could call <code>activate()</code> directly. Multimeter still supports macOS 13, so moving it over takes a check:</p>\n<figure class=\"code\" data-lang=\"swift\"><figcaption><span class=\"lang\">Swift</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">extension</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> NSApplication</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    func</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> activateForWindow</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> #available</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">macOS</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 14</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">*</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">            activate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">        } </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">else</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">            activate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">ignoringOtherApps</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">true</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">        }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre></figure><h2 id=\"makekeyandorderfront-vs-orderfrontregardless\" data-level=\"3\">makeKeyAndOrderFront Vs orderFrontRegardless</h2>\n<p><code>orderFrontRegardless()</code> looks like it was made for this. Apple’s documentation says it moves the window “to the front of its level, even if its application isn’t active, without changing either the key window or the main window.” That last part is the catch for a Settings window. In my test app, after <code>orderFrontRegardless()</code> the window was visible, it wasn’t key and the app wasn’t active, so typing still went to the other app.</p>\n<p>Use <code>orderFrontRegardless()</code> for a panel you only need to see, such as a HUD or a status overlay. For a window with text fields or keyboard shortcuts, activate the app and use <code>makeKeyAndOrderFront(nil)</code>.</p>\n<h2 id=\"swiftuis-settings-scene\" data-level=\"3\">SwiftUI’s Settings Scene</h2>\n<p>SwiftUI has two ways to open the <code>Settings</code> scene from code, and both need macOS 14: <code>SettingsLink</code>, a view you put in a menu or window, and the <code>openSettings</code> environment action. The older trick of sending <code>showSettingsWindow:</code> up the responder chain doesn’t open it. In my <code>LSUIElement</code> test app on macOS 27, <code>NSApp.sendAction(Selector((&quot;showSettingsWindow:&quot;)), to: nil, from: nil)</code> returned <code>true</code>, and no window appeared.</p>\n<p><code>openSettings</code> does open the window, but it doesn’t activate the app. In the test, calling it from a SwiftUI view in an <code>LSUIElement</code> app opened the Settings window with <code>isKeyWindow</code> <code>false</code> and <code>NSApp.isActive</code> <code>false</code>. So you still call <code>NSApp.activate()</code> yourself, before or right after.</p>\n<p>The other problem is reaching <code>openSettings</code> at all. It’s an environment value, so only a SwiftUI view can read it. Multimeter builds its menu with <code>NSStatusItem</code> and <code>NSMenu</code>, which aren’t views. Shake It On and Refrain use <code>MenuBarExtra</code>, where <code>SettingsLink</code> would fit, but all three host their SwiftUI settings view in an <code>NSWindow</code> they own and show it with an activate-and-raise helper like the ones above:</p>\n<figure class=\"code\" data-lang=\"swift\"><figcaption><span class=\"lang\">Swift</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">let</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> window </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> NSWindow</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    contentRect</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">NSRect</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">x</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">y</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">0</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">width</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">480</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">height</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">640</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    styleMask</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: [.titled, .closable, .miniaturizable],</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    backing</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: .buffered,</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    defer</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">false</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">window.title </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> \"Settings\"</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">window.contentView </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> NSHostingView</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">rootView</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">SettingsView</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">())</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">window.isReleasedWhenClosed </span><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> false</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">window.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">center</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">()</span></span></code></pre></figure><p>Multimeter’s <code>App</code> body still declares <code>Settings { EmptyView() }</code>, because a SwiftUI <code>App</code> needs at least one scene. Owning the window also means keeping a reference to it, so the second click on Settings brings back the same window instead of opening another.</p>\n<h2 id=\"borrowing-a-dock-icon-while-a-window-is-open\" data-level=\"3\">Borrowing A Dock Icon While A Window Is Open</h2>\n<p>You can also change the activation policy at runtime. <code>setActivationPolicy(_:)</code> has accepted any policy since OS X 10.9 and returns whether it worked. Switching to <code>.regular</code> while Settings is open gives the app a Dock icon and its own menu bar, like any other app with a window up. Switch back when the window closes:</p>\n<figure class=\"code\" data-lang=\"swift\"><figcaption><span class=\"lang\">Swift</span></figcaption><pre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">func</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> showSettings</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    NSApp.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">setActivationPolicy</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(.regular)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    NSApp.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">activate</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    settingsWindow.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">makeKeyAndOrderFront</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">nil</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">// NSWindowDelegate</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">func</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> windowWillClose</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">_</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> notification: Notification) {</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    NSApp.</span><span style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">setActivationPolicy</span><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(.accessory)</span></span>\n<span class=\"line\"><span style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre></figure><p>If the app can have more than one window open, only switch back when the last one closes, or the Dock icon disappears while a window is still up. In an app where I let people turn the Dock icon on and off, setting <code>.accessory</code> is followed by <code>NSApp.activate(ignoringOtherApps: true)</code> on the next run loop pass, so an open Settings window stays in front.</p>\n<p>None of our shipping apps flip the policy for Settings. Activating the app plus <code>makeKeyAndOrderFront(nil)</code> covers it, and on a macOS 14 target that’s <code>NSApp.activate()</code> followed by <code>window.makeKeyAndOrderFront(nil)</code> in the menu item’s action.</p>","created_at":"2026-11-03T14:00:00.000Z","updated_at":"2026-11-03T14:00:00.000Z"}