Open sindresorhus opened 6 months ago
Apple reply:
Thank you for your feedback.
In general, SwiftUI will call a View's body property only when it needs to. Typically, this will be due to some state that the view depends on being updated.
Therefore, it is best to think of your views as a function of state.
With the MenuBarExtra example you have provided here, there is no state being updated, and as such, SwiftUI will not call body unnecessarily, regardless of whether the menu is being re-presented.
If we were to add a dependency on state, say for example a value that updates periodically via a timer:
@Observable
final class Counter {
static var shared = Counter()
init() {
let timer = Timer(timeInterval: 1, repeats: true) { _ in
self.value += 1
}
RunLoop.main.add(timer, forMode: .common)
}
var value = 1
}
@main
struct SampleApp: App {
var body: some Scene {
MenuBarExtra {
MenuContent()
} label: {
Text("Extra Label")
}
}
}
struct MenuContent: View {
let counter = Counter.shared
var body: some View {
Text("Value: \(counter.value)")
}
}
Then, we will see body called when the menu is displayed, since it is now dependent on some state that is changing.
We hope this helps to better define your desired use case.
My reply:
Thanks for the reply. Much appreciated. I'm aware of the workarounds. I'm arguing that opening the menu is in fact a state change, although an external state change. Same as changing the system locale in System Settings is an external state change that does trigger a view update. Because of FB13683950, there is currently no good way to actually update the menu when it opens. FB13683950 may as well be a better solution, and I would be fine with that too.
Regarding the provided example. I believe that's an anti-pattern because the counter will cause the view to update every 1 second even when the menu is closed, so it will waste a lot of system resources. Prepend let _ = print("UPDATE")
to the MenuContent
body and run the app, and you will see that it keeps updating even when the menu is not shown.
Hey mate -
I just ran into this myself and was stumped for a few hours. I think I've found a nice solution that doesn't require polling, and basically means that when the menu bar extra's view content appears you can react accordingly
The Environment has an isPresented
member that toggles when a view appears or disappears.
So you can set an .onChange(of: isPresented) { ... }
within your view.
struct MenuBarExtraView: View {
@Environment(\.isPresented) var isPresented
var body: some View {
Button(...)
.onChange(of: isPresented) { oldValue, newValue in
if newValue {
... do something ...
}
}
}
}
Anyway, hope this is useful.
Ouch. This has suddenly stopped working - and I have no idea why.
UPDATE: isPresented
only changes IF the menubarextra application is the frontmost application. For a menubarextra it is most likely not (I have additional windows in my app). Boo!
Description
This would be useful so that it would show fresh content when the menu is opened. For example, the current date. In AppKit land, most menu bar apps call
NSMenu#removeAllItems()
to recreate the menu on open. This IMHO makes sense for menu bar apps, as they are long running, and you don’t really want to keep updating them in the background when they are only used once in a while (waste of resources). You want to update them when the user actually opens the menu to the content.Basically, in the below example, I would want it to print each time the menu is opened:
Related FB13683950, although I think both proposals would be useful.