I'm trying to do a simple application in SwiftUI taking advantage of SwiftUI 2.0's new multiplatform project template, and I wish to add the option to collapse the sidebar as many other apps do.
I tried adding a boolean state variable that controls whether the sidebar should show or not, but that doesn't work because then the main view is turned translucent (I guess this is because macOS thinks the sidebar is now the main view).
Is there a way to natively achieve this in SwiftUI? Please note I'm using macOS Big Sur, Xcode 12, and SwiftUI 2.0
Thanks in advance.
My code
ContentView.swift
import SwiftUI
struct ContentView: View {
#if os(iOS)
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif
@ViewBuilder var body: some View {
#if os(iOS)
if horizontalSizeClass == .compact {
TabController()
} else {
SidebarNavigation()
}
#else
SidebarNavigation()
.frame(minWidth: 900, maxWidth: .infinity, minHeight: 500, maxHeight: .infinity)
#endif
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.sizeThatFits)
}
}
Sidebar.swift
import SwiftUI
struct SidebarNavigation: View {
enum HyperspaceViews {
case home
case localTimeline
case publicTimeline
case messages
case announcements
case community
case recommended
case profile
}
@State var selection: Set<HyperspaceViews> = [.home]
@State var searchText: String = ""
@State var showComposeTootView: Bool = false
@State var showNotifications: Bool = false
@State private var showCancelButton: Bool = false
var sidebar: some View {
VStack {
HStack {
TextField("Search...", text: $searchText)
.cornerRadius(4)
}
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
List(selection: self.$selection) {
Group {
NavigationLink(destination: Timeline().container.frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Home", systemImage: "house")
}
.accessibility(label: Text("Home"))
.tag(HyperspaceViews.home)
NavigationLink(destination: Text("Local").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Local", systemImage: "person.2")
}
.accessibility(label: Text("Local"))
.tag(HyperspaceViews.localTimeline)
NavigationLink(destination: Text("Public").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Public", systemImage: "globe")
}
.accessibility(label: Text("Public"))
.tag(HyperspaceViews.localTimeline)
NavigationLink(destination: Text("Messages").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Messages", systemImage: "bubble.right")
}
.accessibility(label: Text("Message"))
.tag(HyperspaceViews.localTimeline)
Divider()
NavigationLink(destination: Text("Announcements").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Announcements", systemImage: "megaphone")
}
.accessibility(label: Text("Announcements"))
.tag(HyperspaceViews.announcements)
NavigationLink(destination: Text("Community").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Community", systemImage: "flame")
}
.accessibility(label: Text("Community"))
.tag(HyperspaceViews.community)
NavigationLink(destination: Text("Recommended").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("Recommended", systemImage: "star")
}
.accessibility(label: Text("Community"))
.tag(HyperspaceViews.recommended)
Divider()
NavigationLink(destination: Text("Recommended").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Label("hyperspacedev", systemImage: "tag")
}
.accessibility(label: Text("Community"))
.tag(HyperspaceViews.recommended)
}
}
.overlay(self.profileButton, alignment: .bottom)
.listStyle(SidebarListStyle())
}
}
var profileButton: some View {
VStack(alignment: .leading, spacing: 0) {
Divider()
NavigationLink(destination: ProfileView().container.frame(maxWidth: .infinity, maxHeight: .infinity)) {
HStack {
Image("amodrono")
.resizable()
.clipShape(Circle())
.frame(width: 25, height: 25)
Text("amodrono")
.font(.headline)
}
.contentShape(Rectangle())
}
.accessibility(label: Text("Your profile"))
.padding(.vertical, 8)
.padding(.horizontal, 16)
.buttonStyle(PlainButtonStyle())
}
.tag(HyperspaceViews.profile)
}
var body: some View {
NavigationView {
#if os(macOS)
sidebar.frame(minWidth: 100, idealWidth: 180, maxWidth: 200, maxHeight: .infinity)
#else
sidebar
#endif
Text("Content List")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.sheet(isPresented: self.$showComposeTootView) {
ComposeTootView(showComposeTootView: self.$showComposeTootView)
.frame(minWidth: 400, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
}
.toolbar {
ToolbarItem {
Button(action: {
self.showNotifications.toggle()
}) {
Image(systemName: "bell")
}
.popover(
isPresented: self.$showNotifications,
arrowEdge: .bottom
) {
LazyVStack {
ForEach(0 ..< 10 ) { i in
Label("@\(i) liked your post!", systemImage: "hand.thumbsup")
.padding()
Divider()
}
}
}
}
ToolbarItem {
Button(action: {
self.showComposeTootView.toggle()
}) {
Image(systemName: "square.and.pencil")
}
}
}
}
}
struct SidebarNavigation_Previews: PreviewProvider {
static var previews: some View {
SidebarNavigation()
}
}
This worked for me - https://developer.apple.com/forums/thread/651807
struct SwiftUIView: View {
var body: some View {
NavigationView{
}.toolbar {
ToolbarItem(placement: .navigation) {
Button(action: toggleSidebar, label: {
Image(systemName: "sidebar.left")
})
}
}
}
}
func toggleSidebar() {
NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
It shows in the views on iOS so you will need some conditions for macOS only.
Apple provide NSToolbarToggleSidebarItemIdentifier
which perform the toggleSidebar(_:)
method.
.onAppear {
NSApp.keyWindow?.toolbar?.insertItem(withItemIdentifier: .toggleSidebar, at: 0)
}
This way will appear a button in your toolbar which action will invoke the sidebar toggle and it works perfectly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With