UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to open Settings from menu bar app and show app icon in dock?

Forums > macOS

I'm creating menu bar app in SwiftUI and I want to add settings. Currently I have two problems:

  1. SettingsLink does open settings app, but if it's already open then it won't bring it to front
  2. When I open settings via SettingsLink, how do I handle showing app icon on dock?

Also, for the second point, I've tried setting Application is agent in Info.plist and changing NSApp.setActivationPolicy() depending if Settings window is open. However, I couldn't figure how to listen to the Settings window open/closed state.

import SwiftUI

@main
struct TestApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate

    var body: some Scene {
        Settings {
            Text("123")
        }
        MenuBarExtra("Test", systemImage: "star.fill")
        {
            SettingsLink { Text("Open settings") }
        }
    }
}

final class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_: Notification) {
        NSApp.setActivationPolicy(.accessory)
    }
}

   

@sax  

Have you found a solution to this? I'm in exactly the same situation, and am just starting to investigate how to solve it.

   

@sax  

Answering my own question, I think I've got a working version.

Settings {
  Text("123")
  .onReceive(NotificationCenter.default.publisher(for: NSWindow.willCloseNotification)) { newValue in
    NSApp.setActivationPolicy(.accessory)
    NSApp.deactivate()
  }
  .onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeMainNotification)) { newValue in
    NSApp.activate(ignoringOtherApps: true)
    NSApp.setActivationPolicy(.regular)
  }
}

However this only seems to work consistently when closing and re-opening the window when I also have the AppDelegate assigned from your example and I apply .keyboardShortcut(",", modifiers: .command) to my SettingsLink.

I've found a number of other suggestions on various forums, including extending Task, or using .onAppear and .onDisappear. So far I've found that none of those works consistently, nor any other thing that I can find using newer SwiftUI capabilities. I wouldn't be surprised if my "working" code winds up being problematic in some way or randomly stops working, based on fiddling with things that other people say worked for them.

   

@sax  

Maybe this as well on the SettingsLink

.onSubmit {
  NSApp.activate(ignoringOtherApps: true)
}

   

@sax  

...nope. Still all sorts of weirdness with the above suggestions.

  • NSWindow.willCloseNotification triggers when mousing through the system menu items for the app. That needs to go.
  • .onSubmit does not actually trigger for SettingsLink. That needs to go.

Something that seems to almost work consistently is to add this library: https://github.com/orchetect/SettingsAccess

SettingsLink {
  Text("Settings")
} preAction: {
  NSApp.activate(ignoringOtherApps: true)
} postAction: {
  NSApp.activate(ignoringOtherApps: true)
}

This also keeps the .onReceive for NSWindow.didBecomeMainNotification, but with an addition:

Settings {
  Text("123")
  .onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeMainNotification)) { newValue in
    NSApp.activate(ignoringOtherApps: true)
    NSApp.setActivationPolicy(.regular)
    NSApp.windows.first?.orderFrontRegardless()
  }
}

For making the app disappear from the dock upon closing the last window, using the AppDelegate as follows seems to do the trick. Unclear as of yet if there are any side effects:

final class AppDelegate: NSObject, NSApplicationDelegate {
  func applicationDidFinishLaunching(_: Notification) {
    NSApp.setActivationPolicy(.accessory)
  }

  func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
    NSApp.setActivationPolicy(.accessory)
    NSApp.deactivate()
    return false
  }
}

   

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.