Replies: 13 comments 2 replies
-
Can you give specific code examples where this is happening? I am able to get
Yes, that's what it means. It's programmatic without requiring user interaction anywhere within the SwiftUI view hierarchy, but not outside of it. There is also one known exception which is detailed in the readme regarding usage within a menu-based MenuBarExtra. Otherwise, it works programmatically as described.
I appreciate the research. The method that is employed in this library is less brittle than synthesizing key events or walking menus for a number of reasons. |
Beta Was this translation helpful? Give feedback.
-
eg struct ContentView: View {
@Environment(\.openSettings) private var openSettings
var body: some View {
ZStack {
Button("Open Settings") { openSettings() }
}.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
openSettings() // triggers "this shouldn't happen in sonoma" assertion
}
}
}
} fwiw i came to this library looking for a replacement for totally get that these solutions are (carefully engineered) hacks, we're working with the scraps that apple calls a platform here |
Beta Was this translation helpful? Give feedback.
-
Ah I know what's happening there.
In your example, This is a good edge case revealing a weakness. What I can probably do is refactor
That is AFAIK not possible as of macOS 14 when using SwiftUI Settings scene, other than some risky workarounds like UI scripting or NSEvents (which could all break in future macOS updates, but who even knows). My goal was to find a more canonical or SwiftUI-compatible way to achieve this.
Absolutely. I filed a radar with Apple asking for their private _openSettings method to be made public which would not be a perfect solution but a pretty good one. I'm not sure we can really ask for more. The more people that file radars the better though. |
Beta Was this translation helpful? Give feedback.
-
I've implemented the fix and tested it - it works correctly when |
Beta Was this translation helpful? Give feedback.
-
Ok pushed release 1.3.0. |
Beta Was this translation helpful? Give feedback.
-
nice! ok this works well enough for me, thanks! it'd be even better for me with here's the setup i ended up with to recreate the global call apple broke: // in @main SwiftUI app, among other WindowGroups & MenuBarExtras
WindowGroup("Open Settings") {
FakeViewToOpenSettingsInSonomaThanksAppleView(title: "Open Settings")
.openSettingsAccess()
}
.handlesExternalEvents(matching: Set(arrayLiteral: "settings"))
.windowStyle(HiddenTitleBarWindowStyle())
.windowResizability(.contentSize) struct FakeViewToOpenSettingsInSonomaThanksAppleView: View {
@Environment(\.openSettings) private var openSettings
var title: String
var body: some View {
ZStack {}
.frame(width: 0, height: 0)
.onAppear {
DispatchQueue.main
/// NB: calling `openSettings` immediately doesn't work so wait a quick moment
.asyncAfter(deadline: .now() + 0.05) {
openSettings()
NSApplication.shared
.windows
.filter { $0.canBecomeKey }
.filter { $0.title == title }
.forEach { $0.close() }
}
}
}
} ...setup a URL scheme as normal in info.plist... and finally global programmatic opening settings like so: NSWorkspace.shared.open(URL(string: "$YOUR-SCHEME-HERE://settings")!) |
Beta Was this translation helpful? Give feedback.
-
Agreed - I'm working on an update to refactor things so the errors are informative and will remove the assert.
A custom URI is clever and a great idea. Especially since it could take a parameter to open Settings and then switch to a specific settings tab page in the window (conceivably). I wish there was a cleaner way to do it -- unfortunate that it needs strapping on a WindowGroup with the window close logic. The Settings {
SettingsView()
}
.handlesExternalEvents(matching: ["settings"]) |
Beta Was this translation helpful? Give feedback.
-
i tried adding handlesExternalEvents to the Settings scene with no success but could've been missing something idk |
Beta Was this translation helpful? Give feedback.
-
Ok I've pushed the changes to main branch. I also improved some of the internals and the error handling will also throw if pre-Sonoma selectors fail. Do you want to try it and see if it works for you? |
Beta Was this translation helpful? Give feedback.
-
Side point: While I understand the desire to have a truly global method to open Settings, is it actually necessary? The Settings window is a part of the UI. As far as design architecture, it should be sufficient to invoke it from another part of the UI (which is possible with SettingsAccess with only few limitations). If you are wanting to open Settings from logic (ie: a non-UI package holding data model structures) then it seems like something that should be surfaced out of the model to the UI, then the UI can handle opening Settings. Are there any use cases you are finding where you absolutely need a global method? |
Beta Was this translation helpful? Give feedback.
-
totally agree aesthetically-- my situation is (i suspect) quite unique, i'm augmenting the javascript runtime on the web with a browser extension that uses a native socket to perform RPCs in a native macOS application, and in some cases need to open settings for the macOS application from ui that's triggered by... an html button in the browser 👹 at the same time, for like 20 years opening settings on macOS has been this globally accessible single line of code that you just call et voila and i think that offering that back is a noble goal |
Beta Was this translation helpful? Give feedback.
-
Ah yeah, I'm no stranger to unique situations.
Not noble enough for Apple it seems - I hope they listen and give us more native options. |
Beta Was this translation helpful? Give feedback.
-
Hey guys, great discussion going on here! Thanks to this issue here I found a workaround to this exact problem. My situation is that I have a macOS app with a mixture of SwiftUI and AppKit. Settings are made with SwiftUI, but I have a classic AppKit |
Beta Was this translation helpful? Give feedback.
-
Bug Description, Steps to Reproduce, Crash Logs, Screenshots, etc.
hey there, thanks for publishing this. your "programmatic" example is called from the action handler of a Button and i found that without the Button click the same code fails into a trigger of the assertion about "this code shouldn't run on sonoma, file a bug report" (or whatever the verbiage is). i expected programmatic to mean "invokable in the absence of user interaction"
but good news is, with the help of chatgpt, i found a solution that works and am sharing it back here in case you'd like to alter the programmatic invocation :)
for any other passersby reading this, if you found this snippet helpful please consider joining me in a prayer: dear god we come to you in a moment of enormous frustration to ask for the special blessing of a pock on the houses of everyone at apple who failed to stop SettingsLink from breaking programmatically opening settings, o lord may you bless us with one thousand generations of those apple engineers' lineages stubbing their toes every day of their lives, each and every one, in the name of all that is holy we offer this humble prayer amen 🙏
P.S. it's also possible some apps/users may have re-mapped ⌘+, and/or the shortcut for Settings, so in that case a fallback that queries the menu for the shortcut could be a good idea:
Beta Was this translation helpful? Give feedback.
All reactions