Collapse sidebar in SwiftUI (Xcode 12)
Asked Answered
D

2

9

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()
    }
}
Dorsal answered 25/6, 2020 at 14:2 Comment(1)
Im having trouble trying to re-enable the sidebar?? I added it into my Mac app. Collapsed it to test and now I cannot get it back within my simulator?Regan
B
18

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.

Boohoo answered 12/7, 2020 at 10:6 Comment(5)
Yeah I also saw that, I'm the author of that question lol, but thanks anyway! :DDorsal
Well thank you then! No idea how many times I lost my sidebar and struggled to get it back until I found that post.Boohoo
Next time, if you forget to implement the button, and you lose the sidebar, you can get it back by removing your application's configuration file from Library/Application Support.Dorsal
Thanks, such a waste of time that I had to spend 20 mins trying to figure what I was doing wrong.Peltz
This does not work in Catalyst though, NSSplitViewController.toggleSidebar is not available. Any ideas for Catalyst?Monsignor
F
1

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.

Festinate answered 29/6, 2020 at 19:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.