Taylor
Taylor

Reputation: 6430

SwiftUI macOS right sidebar inspector

I have a document-based SwiftUI app. I'd like to make a inspector sidebar like the one in Xcode.

Starting with Xcode's Document App template, I tried the following:

struct ContentView: View {
    @Binding var document: DocumentTestDocument
    @State var showInspector = true

    var body: some View {
        HSplitView {
            TextEditor(text: $document.text)
            if showInspector {
                Text("Inspector")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
        }
        .toolbar {
            Button(action: { showInspector.toggle() }) {
                Label("Toggle Inspector", systemImage: "sidebar.right")
            }
        }
    }
}

Which yielded:

Screenshot

How can I extend the right sidebar to full height like in Xcode?

NavigationView works for left-side sidebars, but I'm not sure how to do it for right-side sidebars.

Upvotes: 26

Views: 3946

Answers (2)

Interior Night
Interior Night

Reputation: 1225

Still in beta but out soon with iOS 17, tvOS17 and macOS14, SwiftUI will provide exactly this type of inspector:

ContentView()
    .inspector($showInspector) {
        InspectorView()
    }

Here is a video from WWDC23: https://developer.apple.com/videos/play/wwdc2023/10161

And a link to the docs: https://developer.apple.com/documentation/corelocationui/locationbutton/4200336-inspector/

Upvotes: 5

Chuck H
Chuck H

Reputation: 8276

Here is some stripped down code that I have used in the past. It has the look and feel that you want.

It uses a NavigationView with .navigationViewStyle(.columns) with essentially three panes. Also, the HiddenTitleBarWindowStyle() is important.

The first (navigation) pane is never given any width because the second (Detail) pane is always given all of the width when there is no Inspector, or all of the width less the Inspector's width when it's present. The ToolBar needs to be broken up and have its contents placed differently depending on whether the Inspector is present or not.

@main
struct DocumentTestDocumentApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: DocumentTestDocument()) { file in
            ContentView(document: file.$document)
        }
        .windowStyle(HiddenTitleBarWindowStyle())
    }
}

struct ContentView: View {
    @Binding var document: DocumentTestDocument
    @State var showInspector = true
    var body: some View {
        GeometryReader { window in
            if showInspector {
                NavigationView {
                    TextEditor(text: $document.text)
                        .frame(minWidth: showInspector ? window.size.width - 200.0 : window.size.width)
                        .toolbar {
                            LeftToolBarItems(showInspector: $showInspector)
                        }
                    Inspector()
                        .toolbar {
                            RightToolBarItems(showInspector: $showInspector)
                        }
                }
                .navigationViewStyle(.columns)
            } else {
                NavigationView {
                    TextEditor(text: $document.text)
                        .frame(width: window.size.width)
                        .toolbar {
                            LeftToolBarItems(showInspector: $showInspector)
                            RightToolBarItems(showInspector: $showInspector)
                        }
                }
                .navigationViewStyle(.columns)
            }
        }
    }
}

struct LeftToolBarItems: ToolbarContent {
    @Binding var showInspector: Bool
    var body: some ToolbarContent {
        ToolbarItem(content: { Text("test left toolbar stuff") } )
    }
}

struct RightToolBarItems: ToolbarContent {
    @Binding var showInspector: Bool
    var body: some ToolbarContent {
        ToolbarItem(content: { Spacer() } )
        ToolbarItem(placement: .primaryAction) {
            Button(action: { showInspector.toggle() }) {
                Label("Toggle Inspector", systemImage: "sidebar.right")
            }
        }
    }
}

struct Inspector: View {
    var body: some View {
        VStack {
            Text("Inspector Top")
            Spacer()
            Text("Bottom")
        }
    }
}

Upvotes: 4

Related Questions