🦋
SwiftUI: MenuBarExtraの中でSettingsLinkを使う際にウインドウが最前面に表示されるようにする
macOS Sonoma以前は
NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
を使えばSettings
のウインドウを呼び出せていましたが、Sonoma以降ではSettingsLink
を使わないと設定ウインドウを開くことができなくなり大変不便になりましたね。
なにより、MenuBarExtra
でメニューを表示している時はアプリはフォアグラウンドにならないため、そのままSettingsLink
を使ってもウインドウが最前面に表示されないというのがユーザビリティ的にも最悪です。
メニューバー常駐型アプリの開発において、macOS Sonoma対応の難所となっていたのですが、同様のことに悩んでいた有志のOSS(orchetect/SettingsAccess)にヒントがあり、SettingsLink
を押したときにアプリをフォアグラウンドにする方法を実装できました。
ButtonStyle
を用いるとボタンの見た目をカスタマイズできますが、PrimitiveButtonStyle
を用いればボタンのアクションをカスタマイズできます。しかもSettingsLink
は特殊なButton
であるため、ButtonStyle
の適用が可能です。そのため、SettingsLink
押下時にNSApp.activate(ignoringOtherApps: true)
を発火できるようにすれば良いようです。
PremitiveButtonStyleの実装
@available(macOS 14.0, *)
struct PreActionButtonStyle: PrimitiveButtonStyle {
let preAction: () -> Void
init(preAction: @escaping () -> Void) {
self.preAction = preAction
}
func makeBody(configuration: Configuration) -> some View {
Button(role: configuration.role) {
preAction()
configuration.trigger()
} label: {
configuration.label
}
}
}
@available(macOS 14.0, *)
struct PreActionButtonStyleModifier: ViewModifier {
let preAction: () -> Void
init(preAction: @escaping () -> Void) {
self.preAction = preAction
}
func body(content: Content) -> some View {
content.buttonStyle(PreActionButtonStyle(preAction: preAction))
}
}
@available(macOS 14.0, *)
extension View {
func preActionButtonStyle(preAction: @escaping () -> Void) -> some View {
modifier(PreActionButtonStyleModifier(preAction: preAction))
}
}
作ったButtonStyleを適用する
if #available(macOS 14.0, *) {
SettingsLink {
Text("settings")
}
.preActionButtonStyle {
NSApp.activate(ignoringOtherApps: true)
}
}
なぜかXcodeがアクティブな時はフォーカスがうまく機能しませんが、それ以外のアプリがアクティブな時は、自作アプリの方にフォーカスが移って設定ウインドウが最前面に表示されます。
Discussion