I started down this rabbit hole because a SwiftUI button, designated to pop up a menu, with a .buttonStyle(BorderlessButtonStyle())
style wasn't behaving correctly when switching from a light to a dark mode.
Frustrated, I decided to just make my own button. I noticed the SwiftUI button would sort of toggle when popping up the menu and so I want this behaviour for my button as well. Therefore, I need to find out when an NSMenu is closed.
I've tried the answer suggested here ⤵︎
class NSMenuDelegateModified: NSObject, NSMenuDelegate, ObservableObject {
var menu: NSMenu
@Published var isVisible: Bool?
internal init(menu: NSMenu) {
self.menu = menu
super.init()
self.menu.delegate = self
}
func menuDidClose(_ menu: NSMenu) {
isVisible = false
}
func menuWillOpen(_ menu: NSMenu) {
isVisible = true
}
}
Now this will tell me if the menu is visible from within the class, but when I try to print out .isVisible
on an instantiated object of the class, it only returns false.
Is there a way to figure this out?
So I figured out why I can't get true
out of .isVisible
outside of the class! It's because I use menu.popUp(...)
to open up my menu.
It turns out that this function pauses execution for some parts of the app or perhaps the whole main thread (I'm really not sure) until it can return a state ⤵︎
true = user selected something from the menu
false = user hasn't selected anything and the menu closed
You can see more details on Apple's documentation.
With this in mind, it made everything a lot easier! To toggle the button I could simply change the color after calling menu.popUp(...)
.
This would mean that when the menu was popped up, the color wouldn't change again until a state was returned from the function and execution was resumed!
It looks like I messed up the functionality and it was more complicated than I anticipated. Here is what I ended up with below ⤵︎
.opacity(pressed ? 1 : 0.6)
.inactiveWindowTap { pressed in
if popped == nil {
// Only register mouse ups
if !pressed {
popped = menu.popUp(...)
// Execution pauses here and waits till a state is returned
}
self.pressed = pressed
}
else if !pressed {
popped = nil
}
}
.whenHovered { hovering in
if hovering == false, popped != nil {
popped = nil
}
}
This solution is pretty specific to my needs so I hope what is said here still helps someone. Hopefully, someone can come up with a better answer than this.
.opacity(pressed ? 1 : 0.6)
.inactiveWindowTap { pressed in
// Only register mouse ups
if !pressed {
// You can use the bool returned from this function but you don't have to.
menu.popUp(...) // Execution pauses here until a state is found.
}
self.pressed = pressed
}