Better SwiftUI Settings Scene Access on macOS
MIT License
[!NOTE]
Xcode 16 now makes Apple's previously-private
openSettings
environment method public, and back-ports it to macOS 14.While this is welcome progress, it still is only incremental and the vast majority of SettingsAccess' functionality is still needed in many scenarios.
SettingsAccess 2.0.0 adds support for compiling with Xcode 16 by renaming its
openSettings
method toopenSettingsLegacy
. For projects targeting macOS 14+ you may opt to use the new nativeopenSettings
method. For projects targeting older versions of macOS, useopenSettingsLegacy
.
As of macOS 14 Sonoma:
Apple completely removed the ability to open the SwiftUI Settings scene using legacy NSApp.sendAction()
method using the showSettingsWindow:
(macOS 13) or showPreferencesWindow:
(macOS 12 and earlier) selectors. The only available method of opening the Settings scene (apart from the App menu → Settings menu item) is to use the new SettingsLink
view.
This presents two major restrictions:
Settings
scene.Settings
scene. (Update: Xcode 16 adds support but it is still limited.)These restrictions become problematic in many scenarios. Some examples that are currently impossible without SettingsAccess:
MenuBarExtra
and want to have a button that activates the app, opens Settings
, and then also dismisses the window.Settings
scene in response to a user action in your application that requires the user manipulate a setting that may be invalid.openSettingsLegacy()
that can be called anywhere in the view hierarchy to programmatically open the Settings
scene.SettingsLink
which provides two closures allowing execution of arbitrary code before and/or after opening the Settings
scene.See Getting Started below for example usage.
SettingsLink
.openSettingsLegacy()
is not usable within a menu
-based MenuBarExtra
. In that context, the custom SettingsLink
initializer may be used to run code before/after opening the Settings
scene.Add SettingsAccess as a dependency using Swift Package Manager.
In an app project or framework, in Xcode:
Select the menu: File → Swift Packages → Add Package Dependency...
Enter this URL: https://github.com/orchetect/SettingsAccess
In a Swift Package, add it to the Package.swift dependencies:
.package(url: "https://github.com/orchetect/SettingsAccess", from: "2.0.0")
Import the library.
import SettingsAccess
Attach the openSettingsAccess
view modifier to the base view whose subviews needs access to the openSettingsLegacy
method.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.openSettingsAccess()
}
Settings { SettingsView() }
}
}
In any subview where needed, add the environment method declaration. Then the Settings
scene may be opened programmatically by calling this method.
struct ContentView: View {
@Environment(\.openSettingsLegacy) private var openSettingsLegacy
var body: some View {
Button("Open Settings") { try? openSettingsLegacy() }
}
}
If using a menu-based MenuBarExtra
, do not apply openSettingsAccess()
to the menu content. openSettingsLegacy()
cannot be used there due to limitations of SwiftUI.
Instead, use the custom SettingsLink
initializer to add a Settings menu item capable of running code before and/or after opening the Settings
scene.
@main
struct MyApp: App {
var body: some Scene {
MenuBarExtra {
AppMenuView()
// Do not attach .openSettingsAccess()
}
Settings { SettingsView() }
}
}
struct AppMenuView: View {
var body: some View {
SettingsLink {
Text("Settings...")
} preAction: {
// code to run before Settings opens
} postAction: {
// code to run after Settings opens
}
Button("Quit") { NSApp.terminate(nil) }
}
}
Try the Demo example project to see the library in action.
Requires Xcode 15.0 or higher to build.
Once compiled, supports macOS 11.0 or higher.
SettingsLink
is a view that wraps a standard SwiftUI Button
and its action calls a private environment method called _openSettings
which we have no access to publicly. (A radar has been submitted asking Apple to make it public, but that may never happen.)
It is worth noting that due to how SwiftUI Button
works, it is impossible to attach a simultaneous gesture to attempt to detect a button press.
The solution is the use of a custom Button
style which, when applied directly to SettingsLink
, allows us to capture the Button
press action and export a wrapper method as an environment method called openSettings
that we can use. This same button style can also let us run arbitrary code before and/or after the button action is triggered by the user.
More info and a deep-dive can be found in this reddit post.
Coded by a bunch of 🐹 hamsters in a trenchcoat that calls itself @orchetect.
Licensed under the MIT license. See LICENSE for details.
If you enjoy using SettingsAccess and want to contribute to open-source financially, GitHub sponsorship is much appreciated. Feedback and code contributions are also welcome.
Contributions are welcome. Posting in Discussions first prior to new submitting PRs for features or modifications is encouraged.