Is it possible to start the NavigationSplitView in an expanded state on macOS using SwiftUI? - swift

I like the sidebar to be opened at launch.
However when I build and run the app, this is what I get.
So I need to click on the sidebar icon to show it. This is not the behavior I want. Is it possible to change this?

Somehow, without explicitly setting it in code, the app likes to change the column visibility to .detailOnly at launch. To avoid this behavior, I explicitly set it to .all at onAppear
#State private var columnVisibility =
NavigationSplitViewVisibility.all
var body: some View {
NavigationSplitView(columnVisibility: $columnVisibility) {
Text("Side bar")
} detail: {
Text("Main part")
}
.onAppear() {
columnVisibility = .all
}
}

Related

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.

Menu Bar Popover is missing from application's elements tree on macOS

I'm currently trying to write simple UI Tests for an App that comes with a popover in the macOS menu bar. One of the tests is supposed to open the menu bar popover and interact with its content. The problem is that the content seems to be completely absence from the application's element tree.
I'm creating the pop-up like so:
let view = MenuBarPopUp()
self.popover.animates = false
self.popover.behavior = .transient
self.popover.contentViewController = NSHostingController(rootView: view)
…and show/hide it on menu bar, click like this:
if let button = statusItem.button {
button.image = NSImage(named: NSImage.Name("MenuBarButtonImage"))
button.action = #selector(togglePopover(_:))
}
#objc func togglePopover(_ sender: AnyObject?) {
if self.popover.isShown {
popover.performClose(sender)
} else {
openToolbar()
}
}
func openToolbar() {
guard let button = menuBarItem.menuBarItem.button else { return }
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
NSApp.activate(ignoringOtherApps: true)
}
When I dump the element tree, the popover is not present:
[…]
MenuBar, 0x7fef747219d0, {{1089.0, 1.0}, {34.0, 22.0}}
StatusItem, 0x7fef74721b00, {{1089.0, 1.0}, {34.0, 22.0}}
[…]
Everything works when I compile the app and click around, I just can't make it work when it comes to automated UI testing. Any ideas?
Okay, after spending a lot of time with it, this solved my problem.
First, I had to add the Popover to the app's accessibility children like so:
var accessibilityChildren = NSApp.accessibilityChildren() ?? [Any]()
accessibilityChildren.append(popover.contentViewController?.view as Any)
NSApp.setAccessibilityChildren(accessibilityChildren)
However, this didn't seem to solve my problem at first. I'm using an App Delegate in a SwiftUI application. After tinkering with it for quite some time, I figured out that the commands I've added in my App.swift didn't go too well with my changes to the accessibility children in the App Delegate. After removing the commands from the Window Group, everything worked as expected.
#main
struct MyApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
// .commands {
// CommandGroup(replacing: CommandGroupPlacement.appSettings) {
// Button("Preferences...") { showPreferences() }
// }
// }
}
}

How can I make a window take over the entire screen? MacOS SwiftUI

How can I make a window that is the fullscreen of the MacOS screen that goes over the menuBar, and goes over the applications at the bottom. I saw functions like 'setMenuBarVisible'; however this does not work in SwiftUI. Any help would be appreciated
This code will make your app toggle fullscreen on launch.
WindowGroup {
ContentView()
.onAppear {
DispatchQueue.main.async {
if let window = NSApplication.shared.windows.last {
window.toggleFullScreen(nil)
}
}
}
}
I'm not sure if this is the answer you are looking for but.

Toggle Sidebar in Code using SwiftUI NavigationView on iPad

I'm trying to utilize the built-in sidebar from SwiftUI 2.0 by using NavigationView like this:
NavigationView {
MainView()
ListView()
DetailView()
}.navigationBarHidden(true)
But since I want to use my own Custom Back Button, I've hidden the NavigationBar and tried to toggle the sidebar with code which doesn't work.
self.presentationMode.wrappedValue.dismiss()
I've already seen a lot of solutions for macOS:
NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
But I can't seem to find equivalent for iPad, thanks in advance.
I used this code to change the default sidebar settings:
extension UISplitViewController {
open override func viewDidLoad() {
super.viewDidLoad()
self.preferredDisplayMode = .secondaryOnly
self.preferredSplitBehavior = .overlay
}
}
self exposes several sidebar methods and properties that can be used. I hope it will be useful!
So this is not a good long term solution but if you are like me and 100% needed the native approach to work here's how it can be hacked. Using https://github.com/siteline/SwiftUI-Introspect you can find the right view controller in the hierarchy and set the display mode.
Text("Some View").introspectViewController { vc in
guard let splitVC = vc.parent?.parent as? UISplitViewController else {
return
}
splitVC.preferredDisplayMode = .oneBesideSecondary
}
This is BRITTLE but it works.

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.