对此有很多答案。在 2019 年,最好的方法是在 SplitView 窗格上建立约束,然后为约束设置动画。
假设我有一个带有三个窗格的SplitView:leftPane、middlePane、rightPane。我不仅想折叠侧面的两个窗格,还想在某些视图进入或退出时动态调整各个窗格的宽度。
在 IB 中,我为三个窗格中的每一个设置了 WIDTH 约束。 leftPane 和 rightPane 的宽度设置为 250,优先级为 1000 (required)。
在代码中,它看起来像这样:
@class MyController: NSViewController
{
@IBOutlet var splitView: NSSplitView!
@IBOutlet var leftPane: NSView!
@IBOutlet var middlePane: NSView!
@IBOutlet var rightPane: NSView!
@IBOutlet var leftWidthConstraint: NSLayoutConstraint!
@IBOutlet var middleWidthConstraint: NSLayoutConstraint!
@IBOutlet var rightWidthConstraint: NSLayoutConstraint!
override func awakeFromNib() {
// We use these in our animation, but want them off normally so the panes
// can be resized as normal via user drags, window changes, etc.
leftWidthConstraint.isActive = false
middleWidthConstraint.isActive = false
rightWidthConstraint.isActive = false
}
func collapseRightPane()
{
NSAnimationContext.runAnimationGroup({ (context) in
context.allowsImplicitAnimation = true
context.duration = 0.15
rightWidthConstraint.constant = 0
rightWidthConstraint.isActive = true
// Critical! Call this in the animation block or you don't get animated changes:
splitView.layoutSubtreeIfNeeded()
}) { [unowned self] in
// We need to tell the splitView to re-layout itself before we can
// remove the constraint, or it jumps back to how it was before animating.
// This process tells the layout engine to recalculate and update
// the frames of everything based on current constraints:
self.splitView.needsLayout = true
self.splitView.needsUpdateConstraints = true
self.splitView.needsDisplay = true
self.splitView.layoutSubtreeIfNeeded()
self.splitView.displayIfNeeded()
// Now, disable the width constraint so we can resize the splitView
// via mouse, etc:
self.middleWidthConstraint.isActive = false
}
}
}
extension MyController: NSSplitViewDelegate
{
final func splitView(_ splitView: NSSplitView, canCollapseSubview subview: NSView) -> Bool
{
// Allow collapsing. You might set an iVar that you can control
// if you don't want the user to be able to drag-collapse. Set the
// ivar to false usually, but set it to TRUE in the animation block
// block, before changing the constraints, then back to false in
// in the animation completion handler.
return true
}
final func splitView(_ splitView: NSSplitView, shouldHideDividerAt dividerIndex: Int) -> Bool {
// Definitely do this. Nobody wants a crappy divider hanging out
// on the side of a collapsed pane.
return true
}
}
你可以在这个动画块中变得更复杂。例如,您可以决定要折叠右侧窗格,同时还要将中间的窗格放大到 500 像素。
与此处列出的其他方法相比,这种方法的优势在于它会自动处理窗口框架当前不够大以容纳“展开”折叠窗格的情况。另外,您可以使用它以任何方式更改窗格的大小,而不仅仅是展开和折叠它们。您还可以让所有这些变化同时发生在一个流畅的组合动画中。
注意事项:
- 显然构成
leftPane、middlePane 和rightPane 的视图永远不会改变。这些是您根据需要添加/删除其他视图的“容器”。如果您从 SplitView 中删除窗格视图,您将破坏您在 IB 中设置的约束。
- 使用 AutoLayout 时,如果您发现自己手动设置框架,那么您就是在与系统作斗争。你设置约束;自动布局引擎设置框架。
-
-setPosition:ofDividerAtIndex: 方法在 splitView 不够大而无法将分隔线设置在您想要的位置时效果不佳。例如,如果您想取消折叠右侧窗格并为其提供500 宽度,但您的整个窗口当前只有300 宽度。如果您需要一次调整多个窗格的大小,这也会变得很麻烦。
- 您可以在此方法的基础上做更多事情。例如,也许您想为 splitView 中的各种窗格设置最小和最大宽度。使用约束执行此操作,然后根据需要更改
min 和 max 宽度约束的常量(可能当不同的视图进入每个窗格时等)。
重要说明:
如果其中一个窗格中的任何子视图具有优先级为1000 的width 或minimumWidth 约束,则此方法将失败。您将在日志中收到“无法满足约束”通知。您需要确保您的子视图(及其子视图,一直沿层次结构向下)没有在1000 优先级设置宽度约束。对此类约束使用 999 或更少,以便 splitView 始终可以覆盖它们以折叠视图。