Comments (13)
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
Can you give specific code examples where this is happening? I am able to get openSettings()
to work anywhere in the View hierarchy, not just in a Button action. If there's edge cases I'd like to know so the library can be improved.
i expected programmatic to mean "invokable in the absence of user interaction"
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 found a solution that works and am sharing it back here in case you'd like to alter the programmatic invocation
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.
from settingsaccess.
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 NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
which works globally. if that's not the goal of the library, no sweat, just figured it was worth trying to help the library achieve that goal before releasing one that does this.
totally get that these solutions are (carefully engineered) hacks, we're working with the scraps that apple calls a platform here
from settingsaccess.
Ah I know what's happening there.
openSettings()
itself is a closure. By default it is routed to the legacy Settings method. On macOS 14, when SwiftUI sets up a View, then internally openSettings()
gets updated and replaced by the correct call to SettingsLink
internally.
In your example, openSettings()
is getting captured before it can be updated.
This is a good edge case revealing a weakness. What I can probably do is refactor openSettings()
to be a method call instead of a closure itself which would solve this issue.
looking for a replacement for NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil) which works globally
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.
totally get that these solutions are (carefully engineered) hacks, we're working with the scraps that apple calls a platform
here
[...] everyone at apple who failed to stop SettingsLink from breaking programmatically opening settings [...]
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.
from settingsaccess.
I've implemented the fix and tested it - it works correctly when openSettings
is captured now. Will push a release momentarily.
from settingsaccess.
Ok pushed release 1.3.0.
from settingsaccess.
nice! ok this works well enough for me, thanks! it'd be even better for me with throws
instead of the assertion so i can catch and use the NSEvent workaround in that event, are you game for making that change?
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")!)
from settingsaccess.
it'd be even better for me with throws instead of the assertion
Agreed - I'm working on an update to refactor things so the errors are informative and will remove the assert.
here's the setup i ended up with to recreate the global call apple broke:
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
scene itself is capable of using handlesExternalEvents
directly. Haven't tried yet but it's worth exploring.
Settings {
SettingsView()
}
.handlesExternalEvents(matching: ["settings"])
from settingsaccess.
i tried adding handlesExternalEvents to the Settings scene with no success but could've been missing something idk
from settingsaccess.
Ok I've pushed the changes to main branch. openSettings()
is now throwing and gives a specific error reason which might help with diagnosing issues and debugging.
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?
from settingsaccess.
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?
from settingsaccess.
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?
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
from settingsaccess.
Ah yeah, I'm no stranger to unique situations.
globally accessible single line of code [...] is a noble goal
Not noble enough for Apple it seems - I hope they listen and give us more native options.
from settingsaccess.
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?
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 NSMenu
with NSMenuItem
that opens the SwiftUI Settings.
from settingsaccess.
Related Issues (6)
- macOS 14.4.1: Settings no longer orders front when activated HOT 3
- Error in macOS 15.0 Sequoia Beta HOT 4
- `openSettings()` fails in menu-based MenuBarExtra HOT 9
- Current readme.md suggests minimum macOS is 11, this is wrong and should be 13 HOT 1
- Settings window can not be open in a space with a fullscreen app HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from settingsaccess.