有没有办法禁用SidebarListStyle NavigationViews的可折叠性?
编辑:截至2022年末,此方法仍然有效,并且在任何macOS版本(最新的Ventura 13.1版本)上都从未停止工作。不确定为什么这里有一些回答表明情况不同。如果Introspection库更改其API,则可能需要相应地更新调用,但解决方案的要点是相同的。
使用这个SwiftUI Introspection库: https://github.com/siteline/SwiftUI-Introspect
我们可以通过扩展其功能来内省底层的NSSplitView
:
public func introspectSplitView(customize: @escaping (NSSplitView) -> ()) -> some View {
return introspect(selector: TargetViewSelector.ancestorOrSibling, customize: customize)
}
然后在 View
上创建一个通用扩展:
public extension View {
func preventSidebarCollapse() -> some View {
return introspectSplitView { splitView in
(splitView.delegate as? NSSplitViewController)?.splitViewItems.first?.canCollapse = false
}
}
}
可以在我们的侧边栏上使用:
var body: some View {
(...)
MySidebar()
.preventSidebarCollapse()
}
Oskar提到的内省库在MacOS上无法使用。
受此启发,我想出了一种适用于MacOS的解决方案。
该解决方案的合理性在于使用一种微妙的方式来查找当前窗口中NavigationView
的父视图,即NSSplitViewController
。
以下代码已在XCode 13.2和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
}
onAppear
块中的代码包装在 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {}
中,因为如果调用得太早,findNSSplitVIew
将返回 nil
。 - Sebastian虽然Oskar使用Introspect库的方法已经不再适用,但我找到了另一种使用Introspect防止侧边栏折叠的方式。首先,你需要在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
))
}
}
然后执行以下操作:
NavigationView {
SidebarView()
.introspectSplitView { controller in
(controller.delegate as? NSSplitViewController)?.splitViewItems.first?.canCollapse = false
}
Text("Main View")
}