问题描述
我通过继承 NSView
(不使用任何现有控件/按钮;它是一个完全自定义的视图,具有一组复杂的内部约束)创建了一个自定义段控件(Cocoa / macOS),它有两种模式:
- 默认水平显示所有段:[ segment 1 ] [ segment 2 ] [ segment 3 ]i>
- 当所有段都无法适应窗口/当前约束集(受周围控件及其约束的影响)时,将单个段显示为下拉列表:[ 段 1 ? ]i>
这很好用,我可以在运行时在两种模式之间切换/设置动画。但是我最终想要实现的是基于当前窗口大小的自动扩展/压缩(或者在用户调整窗口大小时在两者之间切换)。我希望这个控件在没有窗口/视图控制器管理开关的情况下可以重用,并试图避免在基于来自超级视图的 layout
调用内部的“粗略”估计的约束之间切换(感觉就像一个黑客)。
似乎 NSSegmentControl
、NSButton
等实现了 NSUserInterfaceCompression
,这应该可以实现我想要实现的目标,但是在初始布局期间,该协议中的任何方法都不会被调用 /内在内容大小刷新/窗口调整大小等。我也发现文档缺乏;我发现的唯一有用信息是在 NSSegmentControl
头文件中。该协议似乎正是我所需要的 - 让系统调用适当的方法来确定最小/理想大小,并在空间非常宝贵时要求控件自行调整大小。
就其价值而言,我也尝试过对 NSButton
进行子类化(出于各种原因,我需要坚持对 NSView
进行子类化)-但是这也没有触发任何这些方法(即从NSUserInterfaceCompression
).
知道我错过了什么吗?
解决方法
好奇……稍微搜索一下,我可以找到很少关于NSUserInterfaceCompression
的信息?
不确定您需要做什么,但类似这种方法的方法可能适合您:
class SegTestView: NSView {
let segCtrl = NSSegmentedControl()
var curWidth: CGFloat = 0
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
addSubview(segCtrl)
segCtrl.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
segCtrl.topAnchor.constraint(equalTo: topAnchor),segCtrl.leadingAnchor.constraint(equalTo: leadingAnchor),segCtrl.trailingAnchor.constraint(equalTo: trailingAnchor),segCtrl.bottomAnchor.constraint(equalTo: bottomAnchor),])
}
override func layout() {
super.layout()
// only execute if bounds.width has changed
if curWidth != bounds.width {
curWidth = bounds.width
segCtrl.segmentCount = 3
segCtrl.setLabel("First",forSegment: 0)
segCtrl.setLabel("Second",forSegment: 1)
segCtrl.setLabel("Third",forSegment: 2)
if segCtrl.intrinsicContentSize.width > bounds.size.width {
segCtrl.segmentCount = 1
segCtrl.setLabel("Single ?",forSegment: 0)
} else {
// in case you want to do something else here...
}
}
}
}
,
看来 NSUserInterfaceCompression
是一个死胡同。目前,我已将此报告为关于文档不足的反馈/错误 (FB9062854)。
我最终解决这个问题的方法是:
- 在自定义控件上设置以下内容压缩:
let priorityToResistCompression = contentCompressionResistancePriority(for: .horizontal)
setContentCompressionResistancePriority(priorityToResistCompression,for: .horizontal)
-
控件中的最后一段(内部
NSView
子视图)有一个尾随锚点,其优先级设置为defaultLow
以允许其中断,以便控件可以继续拉伸 -
覆盖
setFrameSize
并确定要显示的最佳模式(压缩、单个片段作为下拉列表或所有片段,如果它们可以水平放置)。然后调用invalidateIntrinsicContentSize()
以重新计算内容大小。 -
使用在上一步中确定的模式,覆盖
intrinsicContentSize
并提供正确的大小(最小压缩版本或所有段都可以容纳的大小)。
通过这种方式,控件将所有这些功能包装到单个 NSView
子类中,并在调整窗口大小时减轻托管此控件的任何超级视图/窗口设置正确大小的责任。