Collapse sidebar in SwiftUI (Xcode 12)

Viewed 5586

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()
    }
}
2 Answers

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.

Related