SwiftUI - 如何禁用侧边栏折叠?

9

找到解决方案了吗? - Diogo Antunes
1
@DiogoAntunes 不是的。最好的做法是添加一个切换按钮,这样用户就不会迷失了,至少可以这样做。 - Shazniq
3个回答

4

编辑:截至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()
}

3

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
}

2
这个方法非常有效!不过我必须将 onAppear 块中的代码包装在 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {} 中,因为如果调用得太早,findNSSplitVIew 将返回 nil - Sebastian
使用延迟,它对我也起作用了,但是它只能工作一次。如果我尝试第二次拖动它,它就会崩溃。我找到的hacky解决方案是在onAppear和onHover上都添加findNSSplitVIew。(不为此感到自豪) - Alexandre

0

虽然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")
                
}

话虽如此,我们并不知道这种方法能够持续多久。苹果公司可能会改变NavigationView的工作方式,这种方法在未来可能会停止工作。

只有当我添加了“controller.splitViewItems.first?.minimumThickness = 150 controller.splitViewItems.first?.maximumThickness = 150”时,这个方法才对我有效。但是,它只在我第一次尝试折叠视图时起作用。我在Jiakang的答案中也遇到了同样的问题。 - Alexandre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接