'SwiftUI - How to disable sidebar from collapsing?
Is there any way to disable collapsibility of SidebarListStyle NavigationViews?
Solution 1:[1]
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()
}
Solution 2:[2]
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
}
Solution 3:[3]
While the method that Oskar used with the Introspect library no longer works, I did find another way of preventing the sidebar from collapsing using Introspect. First, you need to make an extension on View:
extension View {
public func introspectSplitView(customize: @escaping (NSSplitView) -> ()) -> some View {
return inject(AppKitIntrospectionView(
selector: { introspectionView in
guard let viewHost = Introspect.findViewHost(from: introspectionView) else {
return nil
}
return Introspect.findAncestorOrAncestorChild(ofType: NSSplitView.self, from: viewHost)
},
customize: customize
))
}
}
Then do the following:
NavigationView {
SidebarView()
.introspectSplitView { controller in
(controller.delegate as? NSSplitViewController)?.splitViewItems.first?.canCollapse = false
}
Text("Main View")
}
This being said, we don't know how long this will actually work for. Apple could change how NavigationView works and this method may stop working in the future.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Oskar |
Solution 2 | |
Solution 3 | Jonathan |