wigging
wigging

Reputation: 9190

Set width of SidebarView in macOS using SwiftUI

I'm using a NavigationView to display a SidebarView and DetailView in the window of a Mac app:

import SwiftUI

private let fruits = ["🍎 Apple", "🥥 Coconut", "🥭 Mango", "🥝 Kiwi"]

struct SidebarView: View {

    @Binding var selectedFruit: String?

    var body: some View {
        List(fruits, id: \.self, selection: $selectedFruit) { fruit in
            Text("\(fruit)")
        }
        .listStyle(SidebarListStyle())
        .frame(minWidth: 180, idealWidth: 200, maxWidth: 300)
    }
}

struct DetailView: View {

    @Binding var fruit: String?

    var body: some View {
        Text("\(fruit ?? "Default Fruit")")
            .font(.headline)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

struct ContentView: View {

    @State private var selectedFruit: String?

    var body: some View {
        NavigationView {
            SidebarView(selectedFruit: $selectedFruit)
            DetailView(fruit: $selectedFruit)
        }
        .frame(width: 500, height: 400)
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

The width of the SidebarView can be adjusted by dragging the center divider left or right in the window. I would like to have an initial width of 200 defined for the sidebar along with a minimum width of 180 and a max width of 300. I attempted this by setting the frame of the List which does indeed set the max width but the sidebar can still be completely collapsed. Also, the initial width of the sidebar seems to be defined by minWidth not idealWidth.

Upvotes: 13

Views: 5256

Answers (3)

Klajd Deda
Klajd Deda

Reputation: 375

Having been inspired by the answer from MScottWaller, I just added some calculations to the min size for the detailview. This seems to pin the SideBar to between 220 and 380 Works on macOS 126.1/Xcode 14.0.1

The critical piece is the private func minDetailWidth()

    struct ItemListView: View {
        struct ItemRow: View {
            struct ItemView: View {
                let item: Item

                var body: some View {
                    VStack {
                        Image(systemName: item.imageName)
                            .clipShape(Circle())
                            .overlay(Circle().stroke(Color.white, lineWidth: 2))
                            .padding(2)
                            .overlay(Circle().strokeBorder(Color.black.opacity(0.1)))
                            .shadow(radius: 3)
                            .padding(4)

                        Text(item.name)
                            .font(.largeTitle)
                    }
                }
            }

            let item: Item

            var body: some View {
                NavigationLink(destination: ItemView(item: item)) {
                    HStack {
                        Image(systemName: item.imageName)
                            .resizable()
                            .frame(width: 50, height: 50)
                            .clipShape(Circle())
                            .overlay(Circle().stroke(Color.white, lineWidth: 2))
                            .padding(2)
                            .overlay(Circle().strokeBorder(Color.black.opacity(0.1)))
                            .shadow(radius: 3)
                            .padding(4)

                        Text(item.name)
                            .font(.largeTitle)
                    }
                }
            }
        }

        let items: [Item]

        var body: some View {
            List(items) { fruit in
                ItemRow(item: fruit)
            }
        }
    }

    struct Item: Identifiable {
        let id = UUID()
        let name: String
        let imageName: String
        let color: Color
    }

    class Model: ObservableObject {
        @Published var transportation: [Item] = loadTransportation()
        @Published var vegetables: [Item] = loadVegetables()

        static func loadTransportation() -> [Item] {
            return [
                Item(name: "Airplane", imageName: "airplane", color: .orange),
                Item(name: "Car", imageName: "car", color: .blue),
                Item(name: "Bus", imageName: "bus.fill", color: .red),
                Item(name: "Tram", imageName: "tram.fill", color: .pink),
                Item(name: "Ferry", imageName: "ferry", color: .purple),
                Item(name: "Bicycle", imageName: "bicycle", color: .green),
            ]
        }

        static func loadVegetables() -> [Item] {
            return [
                Item(name: "Spinach", imageName: "spinach", color: .red),
                Item(name: "Lettuce", imageName: "lettuce", color: .orange),
                Item(name: "Tomatoes", imageName: "tomatoes", color: .green),
                Item(name: "Onion", imageName: "onion", color: .blue),
            ]
        }
    }

    @ObservedObject private var model = Model()
    private var sideBarMaxWidth = 380.0

    /**
     window width minus the sideBarMaxWidth
     */
    private func minDetailWidth(_ proxy: GeometryProxy) -> Double {
        let rv_ = proxy.size.width - sideBarMaxWidth
        let rv = max(rv_, 480)
        // Log4swift[Self.self].info("\(rv)")
        return rv
    }

    var body: some View {
        GeometryReader { proxy in
            NavigationView {
                List {
                    NavigationLink(
                        "Fruits",
                        destination:
                            ItemListView(items: self.model.transportation)
                            .frame(minWidth: minDetailWidth(proxy), idealWidth: 840, maxWidth: .infinity)
                    )

                    NavigationLink(
                        "Vegetables",
                        destination:
                            ItemListView(items: self.model.vegetables)
                            .frame(minWidth: minDetailWidth(proxy), idealWidth: 840, maxWidth: .infinity)
                    )
                }
                .frame(minWidth: 220, maxWidth: sideBarMaxWidth)

                Text("Nothing Selected")
                    .frame(minWidth: minDetailWidth(proxy), maxWidth: .infinity)
            }
        }
    }
}

struct ExampleView_Previews: PreviewProvider {
    static var previews: some View {
        ExampleView()
        // .background(Color(NSColor.windowBackgroundColor))
            .environment(\.colorScheme, .light)

        ExampleView()
        // .background(Color(NSColor.windowBackgroundColor))
            .environment(\.colorScheme, .dark)
    }
}

Upvotes: 0

BearChao
BearChao

Reputation: 9

Maybe you can set the minWidth for DetailView. I tried it works well.

Upvotes: -2

MScottWaller
MScottWaller

Reputation: 3583

For anyone who finds this and is wanting to know how to set the maxWidth of a sidebar in macOS, you really don't. If you do, the view will stop growing and then be caught in the middle of the sidebar, which will keep expanding.

It took me a minute to figure this out, but what you do is set the minWidth of the other view, the detail view. And the sidebar will grow until it can't. You'll notice that even the sidebar of Finder grows and grows until it hits the minWidth of the detail, so to speak.

Upvotes: 13

Related Questions