25

I have created a simple macOS-only SwiftUI app and added a Settings screen as per Apple's instructions:

import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            RootView()
        }
        Settings {
            SettingsView()
        }
    }
}

It works: the Preferences option shows up in the app menu. Now, I would also like to open this settings window programatically, e.g. when a button is clicked. Is there a way to achieve this?

I was hoping there would be a method in NSApplication, similar to the About window, but there doesn't seem to be one.

Joshua
  • 1,349
  • 15
  • 26

4 Answers4

39

By inspecting the app menu, I found this solution:

NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil)

I assume that it may not pass App Review for using a private API, though, so less shady alternatives are still welcome.

Update: This has actually passed App Review, so I had marked it as the answer.

Update 2: For a macOS 13 solution, see Manny's answer.

Update 3: For macOS 14 with SwiftUI, you must use the new SettingsLink view documented here. NSApp.sendAction no longer works.

greybeard
  • 2,249
  • 8
  • 30
  • 66
Joshua
  • 1,349
  • 15
  • 26
30

On macOS 13+ versions the action name has been renamed to Settings. So to update Joshua's answer, here is the solution to use:

NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)

I wonder if there is a settings scene id we can use with the new openWindow environment method. :)

Manny
  • 301
  • 3
  • 4
9

On macOS 13+, I use this solution:

if #available(macOS 13, *) {
    NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
} else {
    NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil)
}
Alex
  • 124
  • 1
  • 4
0

My take on the answers which have already been given is to probe the application delegate for selector response. This has worked well for me so far:

extension NSApplication {
    
    func openSettings() {
        guard let delegate = NSApplication.shared.delegate else { return }
        
        // macOS 13 Ventura
        var selector = Selector(("showSettingsWindow:"));
        if delegate.responds(to: selector) {
            delegate.perform(selector, with: nil, with: nil);
            return;
        }
        
        // macOS 12 Monterrey
        selector = Selector(("showPreferencesWindow:"));
        if delegate.responds(to: selector) {
            delegate.perform(selector, with: nil, with: nil);
            return;
        }
    }
    
}
stephancasas
  • 615
  • 3
  • 12