I'm writing a MacOS app. I'm displaying a button. When the app is inactive, if I click the button, the button's tap event gets triggered as the same time as the window is activated. So far so good.
However if I now add .buttonStyle(PlainButtonStyle())
to my button which is the desired look, it doesn't get tapped when the window is inactive. The first click activates the window and I need a second click to trigger the button.
Button {
print("tapped!")
} label: {
Image(systemName: "doc.on.doc")
.resizable()
.frame(width: 14, height: 14)
.padding(5)
}
.buttonStyle(PlainButtonStyle())
Whether a view can receive the click event without their window being activated is controlled by the acceptsFirstMouse(for:)
method. When the button style is .plain
, the button is no longer backed by an NSButton
(whose acceptsFirstMouse
method would have returned true
), so you can no longer "click-through" the button.
You can wrap a NSViewRepresentable
around the button, and override acceptsFirstMouse
to return true.
This is the code from this blog post
extension SwiftUI.View {
public func acceptClickThrough() -> some View {
ClickThroughBackdrop(self)
}
}
fileprivate struct ClickThroughBackdrop<Content: SwiftUI.View>: NSViewRepresentable {
final class Backdrop: NSHostingView<Content> {
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
return true
}
}
let content: Content
init(_ content: Content) {
self.content = content
}
func makeNSView(context: Context) -> Backdrop {
let backdrop = Backdrop(rootView: content)
backdrop.translatesAutoresizingMaskIntoConstraints = false
return backdrop
}
func updateNSView(_ nsView: Backdrop, context: Context) {
nsView.rootView = content
}
}
Example usage:
Button("Foo") {
print("triggered")
}
.buttonStyle(.plain)
.acceptClickThrough()