Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - How to disable sidebar from collapsing?

Gif to understand easier

Is there any way to disable collapsibility of SidebarListStyle NavigationViews?

like image 284
Shazniq Avatar asked Oct 26 '22 14:10

Shazniq


2 Answers

Using this SwiftUI Introspection library: https://github.com/siteline/SwiftUI-Introspect

We can introspect the underlying NSSplitView by extending their functionality:

public func introspectSplitView(customize: @escaping (NSSplitView) -> ()) -> some View {
    return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize)
}

And then create a generic extension on View:

public extension View {
    func preventSidebarCollapse() -> some View {
        return introspectSplitView { splitView in
            (splitView.delegate as? NSSplitViewController)?.splitViewItems.first?.canCollapse = false
        }
    }
}

Which can be used on our sidebar:

var body: some View {
    (...)
    MySidebar()
        .preventSidebarCollapse()
}
like image 140
Oskar Avatar answered Nov 15 '22 05:11

Oskar


The introspection library mentioned by Oskar is not working for MacOS.

Inspired by that, I figured out a solution for MacOS.

The rationality behind the solution is to use a subtle way to find out the parent view of a NavigationView which is a NSSplitViewController in the current window.

Below codes was tested on XCode 13.2 and macOS 12.1.

var body: some View {
    Text("Replace with your sidebar view")
       .onAppear {
           guard let nsSplitView = findNSSplitVIew(view: NSApp.windows.first?.contentView), let controller = nsSplitView.delegate as? NSSplitViewController else {
                    return
           }
           controller.splitViewItems.first?.canCollapse = false
                // set the width of your side bar here.
           controller.splitViewItems.first?.minimumThickness = 150
           controller.splitViewItems.first?.maximumThickness = 150
       }
}

private func findNSSplitVIew(view: NSView?) -> NSSplitView? {
        var queue = [NSView]()
        if let root = view {
            queue.append(root)
        }
        while !queue.isEmpty {
            let current = queue.removeFirst()
            if current is NSSplitView {
                return current as? NSSplitView
            }
            for subview in current.subviews {
                queue.append(subview)
            }
        }
        return nil
}
like image 37
Jiakang Zhang Avatar answered Nov 15 '22 06:11

Jiakang Zhang