SwiftUI MenuBar App with Assertion on #main - swift

I have a SwiftUI Menu Bar App with no visible Windows on startup, except an entry in the Menu Bar with a Button showing a View on click.
This works great, but sporadically, the App throws an assertion and the MenuBar Button is not added, though its impossible to click it.
Here is what does not solve the issue:
Cleaning Build + Derived Data
Reboots
Code in AppDelegate (needed for the MenuBar)
#main // Assertion thrown here
struct MenuBarApp: App {
#Environment(\.scenePhase) var scenePhase
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
// This surpresses opening an empty Window on launch
ZStack{
EmptyView()
}.hidden() // removing .hidden() sometimes make the assertion go away, sometimes having .hidden() here triggers it
// Sometimes, a second ZStack makes the assertion dissappear, sometimes it triggers it.
}
}
}
The assertion reads:
2022-05-24 12:58:06.263464+0200 MenuBar[7119:96419] *** Assertion failure in void _NSWindowSetFrameIvar(NSWindow *, NSRect)(), NSWindow.m:935
2022-05-24 13:04:40.477258+0200 MenuBar[7119:96419] Invalid parameter not satisfying: <SwiftUI.SwiftUIWindow: 0x159e63780>. "frame=CGRectContainsRect(CGRectMake((CGFloat)INT_MIN, (CGFloat)INT_MIN, (CGFloat)INT_MAX - (CGFloat)INT_MIN, (CGFloat)INT_MAX - (CGFloat)INT_MIN), frame)"
On other machines the build and launch of the same Code runs perfectly fine.
func applicationDidFinishLaunching(_ notification: Notification) is not getting called in the assert case
macOS 12.4, M1 Max, XCode 13.3.1

Code in AppDelegate (needed for the MenuBar)
The AppDelegate is no longer needed with the release of MenuBarExtra as of Xcode 14.2, macOS 13 Ventura. A MenuBarExtra scene renders a persistent control in the system menu bar with the native SwiftUI lifecycle.
#main
struct MyStatubarMenuApp: App {
var body: some Scene {
// -- no WindowGroup required --
// -- no AppDelegate needed --
MenuBarExtra("😎") {
Text("Hello Status Bar Menu!")
}
}
}

Related

How can I make my Content View Window became borderless in SwiftUI for macOS?

I am using this code for making my Window became borderless, but all I am getting is crash in run time!
struct ContentView: View {
var body: some View {
Button("borderless") {
NSApplication.shared.keyWindow?.styleMask = .borderless
}
}
}
Error:
Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
From Xcode Help:
#note The styleMask can only be set on macOS 10.6 and later. Valid \c styleMask settings have the same restrictions as the \c styleMask passed to -initWithContentRect:styleMask:backing:defer:. Some \c styleMask changes will cause the view hierarchy to be rebuilt, since there is a different subclass for the top level view of a borderless window than for the top level view of a titled window.
Xcode version Version 14.1 (14B47b)
The goal is solving the error that using the code does not crash the app and not changing the question.
A window that uses NSWindowStyleMaskBorderless can’t become key or main, which is why your code doesn't work.
What you could do however is to hide each element of the titlebar instead:
if let window = NSApplication.shared.mainWindow {
window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
}
This assumes you only want to change main window. If you want to change all windows, you should loop all windows instead.

Remember #State variable after switching views

I'm making an application for macOS with SwiftUI. I have a button that starts a download process asynchronously by calling Task.init and an async function. When the task starts, I set a #State busy variable to true so I can display a ProgressView while the download is happening. The problem is if I switch to a different view then back while it is downloading, the state gets reset although the task still runs. How can I make it so it remembers the state, or maybe check if the task is still running?
Here is a stripped down example:
import SwiftUI
struct DownloadRow: View {
let content: Content
#State var busy = false
var body: some View {
if !busy {
Button() {
Task.init {
busy = true
await content.download()
busy = false
}
} label: {
Label("Download")
}
} else {
ProgressView()
}
}
}
You could put the variable that tracks the download's progress into an ObservableObject class and make the progress variable #Published. Have the object that tracks your progress as an #ObservedObject in the view. By doing so, you decouple the progress tracker from the view's lifecycle. Make sure this view does not initialize the progress tracker, or a new object will be built when the view is built again.

Launch windows in a SwiftUI App-based project from custom events on macOS

In a SwiftUI macOS app that uses the SwiftUI App lifecycle I have been struggling to understand how to launch a new window and then assign it some initial state.
Using Xcode 12.5’s new project we get:
import SwiftUI
#main
struct StackOverflowExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
To keep the question simple, let’s say that I would like to launch a new window every minute and send the time the window was launched to the view. For this we can use a Timer like so:
Timer.scheduledTimer(
withTimeInterval: 60,
repeats: true,
block: {_ in
// do something here that launches a new window containing
// ContentView and sends it the current time
}
)
Any ideas on how to proceed?

SwiftUI onMoveCommand actions aren't executed

I'm making a macOS app with SwiftUI, and I would like to offset a SwiftUI view using the arrow keys on the built-in keyboard.
I couldn't find many resources online, but onMoveCommand() appears to be the event handler I need. Upon trying it out, I discovered that the action I specified for onMoveCommand() does not appear to be executed. Here's some code I wrote just to test it out:
struct ContentView: View {
var body: some View {
Text("Hello")
.onAppear() {
print("Appeared!")
}
.onMoveCommand() { (direction) in
print("Moved!")
}
.onTapGesture() {
print("Tapped!")
}
}
}
onMoveCommand() does not print "Moved!" when I press the arrow keys, instead I get the error alert sound played, and nothing is printed. onAppear() successfully prints the "Appeared!" message when the view appears, and onTapGesture() prints "Tapped!" correctly whenever I click the text. This seems to tell me that the basic syntax I got for these view events is correct, but I implemented onMoveCommand() incorrectly.
For now I only want my app to print something to the Xcode console when the arrow keys are pressed, and to be able to distinguish which arrow key was pressed. Can someone please explain what I did wrong?
Keyboard events are handled only by view in focus, so fix is
var body: some View {
Text("Hello")
.focusable() // << here !!
.onAppear() {
print("Appeared!")
}
.onMoveCommand() { (direction) in
print("Moved!")
}
.onTapGesture() {
print("Tapped!")
}
}
Tested with Xcode 11.4 / macOS 10.15.4. Make sure you have turned on keyboard navigation in System Preferences.

SwiftUI - Making a View focusable and trigger an action on macOS

How is it possible to make a View on macOS focusable. I tried the it like below but the action is never triggered. I know that for NSView you have to implement acceptsFirstResponder to return true but can not find similar in SwiftUI.
Is this still a Beta related bug or a missing functionality for macOS ?
struct FocusableView: View {
var body: some View {
Group {
Text("Hello World!")
.padding(20)
.background(Color.blue.cornerRadius(8))
.focusable(true) { isFocused in
print("Focused", isFocused)
}
}
}
}
I think I found a work around for the problem to shift the focus to a SwiftUI view.
I am working on macOS Catalina, 10.15, Swift 5, Xcode 11.1
The problem is to shift the focus to a SwiftUI view. I could imagine that this has not been perfectly implemented, yet.
Only upon shifting the focus to the required SwiftUI view within the SwiftUI framework the onFocusChange closure of a focusable view will be called and the view will be focused.
My intention was: when I click into the SwiftUI view I want to execute the onFocusChange closure and I want it to be focused (for subsequent paste commands)
My SwiftUI view is built into the Window using an NSHostingView subclass - ImageHostingView.
When I add this ImageHostingView to the view hierarchy I define its previous key view:
theImageHostingView.superview.nextKeyView = theImageHostingView
I added a mouseDown handler to the ImageHostingView:
override dynamic func mouseDown(with event: NSEvent) {
if let theWindow = self.window, let theView = self.previousValidKeyView {
theWindow.selectKeyView(following:theView)
}
}
Calling the NSWindow's selectKeyView(following:) triggers within the SwiftUI framework a focus change to the desired view (the root view of ImageHostingView).
This is clearly a work-around and works only for final views which will be represented by a NSHostingView. But it highlights the problem (shifting the focus to a SwiftUI view upon a certain action) and it might be helpful in many cases.
The only way I've been able to get this to work is by checking the "Use keyboard navigation to move focus between controls" checkbox in Keyboard System Preferences