🦋

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の実装
struct PreActionButtonStyle: PrimitiveButtonStyle {
    var 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
        }
    }
}

extension PrimitiveButtonStyle where Self == PreActionButtonStyle {
    static func preAction(perform action: @escaping () -> Void) -> PreActionButtonStyle {
        PreActionButtonStyle(preAction: action)
    }
}
作ったButtonStyleを適用する
SettingsLink {
    Text("settings")
}
.buttonStyle(.preAction {
    NSApp.activate(ignoringOtherApps: true)
})

なぜかXcodeがアクティブな時はフォーカスがうまく機能しませんが、それ以外のアプリがアクティブな時は、自作アプリの方にフォーカスが移って設定ウインドウが最前面に表示されます。

Discussion