1

I'm trying to Build a Mac-App using SwiftUI and I want to replicate the double sidebar feel of XCode, so collapsable sidebar on the left and collapsable inspector on the right.

I achieved this following the answer to this question. But my ToolBarItem is behaving weird in some cases, where it starts jumping to other parts of the NavigationView especially when the right sidebar is open, while collapsing and opening the left one.

The toolbar and window styles are set on app level:

        WindowGroup {
            ContentView()
        }
        .windowToolbarStyle(UnifiedWindowToolbarStyle(showsTitle: false))
        .windowStyle(HiddenTitleBarWindowStyle())

and the toolbars are set using:

struct LeftToolBarItems: ToolbarContent {
    
    @ObservedObject var mbvm = MenuBarViewModel.shared
    
    var body: some ToolbarContent {
        ToolbarItemGroup(placement: .primaryAction) {
            Button {
                mbvm.leftExtended.toggle()
            } label: {
                Image(systemName: "sidebar.leading")
                    .font(.system(size: 17))
            }
        }
    }
}

I'm using a singleton ObservableObject to keep track of the status of the sidebar panes and in the contentView I'm just switching Views based on that status

if (mbvm.leftExtended == true && mbvm.rightExtended == true){
            BothSidebars()
        } else if (mbvm.leftExtended == true && mbvm.rightExtended == false){
            LeftSidebars()
        } else if (mbvm.leftExtended == false && mbvm.rightExtended == true){
            RightSidebars()
        } else {
            NoSidebars()
        }

And those Views are just different alignments of the same underlying views:

struct NoSidebars: View {
    
    @ObservedObject var mbvm = MenuBarViewModel.shared
    
    var body: some View {
        GeometryReader{ window in
            NavigationView{
                PreView()
                    .frame(width: window.size.width)
                    .toolbar {
                        LeftToolBarItems()
                        RightToolBarItems()
                    }
            }
        }
    }
}

struct LeftSidebars: View {
    
    @ObservedObject var mbvm = MenuBarViewModel.shared
    
    var body: some View {
        GeometryReader{ window in
            NavigationView{
                ButtonToolbarView()
                    .toolbar {
                        LeftToolBarItems()
                    }
                PreView()
                    .frame(width: window.size.width-100)
                    .toolbar {
                        RightToolBarItems()
                    }
            }
        }
    }
}

TLDR: How do I prevent my ToolbarItems from switching the panes (the not expected way)?

full code, ready to compile after adding windowStyle

EDIT: updated Pastebin link, weird formatting

newb
  • 172
  • 1
  • 9
  • First, You are using ObservedObject but never pass any object to the views. It is never declared as StateObject. Even using shared does not help. You also need to be careful with NavigationView as the Left TollBar button displayed is not the one you add but the one that column navigation has (you can use a Text instead to see that) – Ptit Xav Sep 22 '22 at 12:59
  • yeah, i guess those where still left from another approach .. But I cant confirm that the left toolbar button isn't mine, changing the label to text, the button present was the one added in the toolbar modifier, and it was still jumping around @PtitXav – newb Sep 22 '22 at 13:29

1 Answers1

1

Some other way to do it, if it can help you (I simplified content view) :

class MenuBarViewModel: ObservableObject {
    @Published var leftExtended: Bool = true
    @Published var rightExtended: Bool = true
    
    // Just to not used published var directly
    func toggleLeftBar() {
        leftExtended.toggle()
    }
    
    func toggleRightBar() {
        rightExtended.toggle()
    }
}

struct LeftToolBarItems: ToolbarContent {
    // must be passed by parent view
    @ObservedObject var mbvm: MenuBarViewModel
    // To toggle visibility
    var show: Bool = true
    
    var body: some ToolbarContent {
        ToolbarItemGroup(placement: .primaryAction) {
            if show {
                Button {
                    mbvm.toggleLeftBar()
                    // EDIT : use underlying split view
                    NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
                } label: {
                    // to test if it is this one and not default one
                    Text("LTB")
                    //                Image(systemName: "sidebar.leading")
                    //                    .font(.system(size: 17))
                }
            }
        }
    }
}

struct RightToolBarItems: ToolbarContent {
    // must be passed by parent view
    @ObservedObject var mbvm: MenuBarViewModel
    // To toggle visibility
    var show: Bool = true
    var body: some ToolbarContent {
        ToolbarItemGroup(placement: .cancellationAction) {
            if show {
                Button {
                    // EDIT :
                    // to animate the change
                    withAnimation {
                        mbvm.toggleRightBar()
                    }
                } label: {
                    // to test if it is this one and not default one
                    Text("RTB")
                    //                Image(systemName: "sidebar.trailing")
                    //                    .font(.system(size: 17))
                }
            }
        }
    }
}

struct ButtonToolbarView: View {
    let columns = [GridItem(), GridItem()]
    
    var body: some View {
        LazyVGrid(columns: columns) {
            // Changed names to be sure it is the good one which is displayeds
            Text("Button")
            Text("Button")
            Text("Button")
            Text("Button")
        }
        .frame(width: 150)
    }
}

struct InspectorToolbarView: View {
    var body: some View {
        VStack{
            // Changed names to be sure it is the good one which is displayeds
            Text("Inspector")
            Text("Inspector")
            Text("Inspector")
            Text("Inspector")
        }
        .frame(width: 200)
    }
}

struct PreView: View {
    var body: some View {
        Rectangle()
            .frame(width: 512, height: 512)
            .background(Color.white)
            .padding()
    }
}

struct ContentViewMac: View {
    @StateObject var mbvm = MenuBarViewModel()
    var body: some View {
       // Navigation view for using the split view system
        NavigationView {
            ButtonToolbarView()
                .toolbar {
                    // the left button position is automatically handled
                    LeftToolBarItems(mbvm: mbvm)
                }
            HStack {
                PreView()
                // right bar management
                if mbvm.rightExtended {
                    InspectorToolbarView()
                }
            }
             // For right bar button always at right
            .toolbar {
                RightToolBarItems(mbvm: mbvm)
            }
        }
        
    }
}

Edit : for a pure MacOS version. Not as good as xCode but to can do it.

Ptit Xav
  • 3,006
  • 2
  • 6
  • 15