自动布局:使用 NSUserInterfaceCompression 的条件内容压缩阻力优先级

问题描述

我通过继承 NSView(不使用任何现有控件/按钮;它是一个完全自定义的视图,具有一组复杂的内部约束)创建了一个自定义段控件(Cocoa / macOS),它有两种模式:

  1. 认水平显示所有段:[ segment 1 ] [ segment 2 ] [ segment 3 ]​​i>
  2. 当所有段都无法适应窗口/当前约束集(受周围控件及其约束的影响)时,将单个段显示为下拉列表:[ 段 1 ? ]​​i>

这很好用,我可以在运行时在两种模式之间切换/设置动画。但是我最终想要实现的是基于当前窗口大小的自动扩展/压缩(或者在用户调整窗口大小时在两者之间切换)。我希望这个控件在没有窗口/视图控制器管理开关的情况下可以重用,并试图避免在基于来自超级视图的 layout 调用内部的“粗略”估计的约束之间切换(感觉就像一个黑客)。

似乎 NSSegmentControlNSButton 等实现了 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)。

我最终解决这个问题的方法是:

  1. 在自定义控件上设置以下内容压缩:
    let priorityToResistCompression = contentCompressionResistancePriority(for: .horizontal)
    setContentCompressionResistancePriority(priorityToResistCompression,for: .horizontal)
  1. 控件中的最后一段(内部 NSView 子视图)有一个尾随锚点,其优先级设置为 defaultLow 以允许其中断,以便控件可以继续拉伸

  2. 覆盖 setFrameSize 并确定要显示的最佳模式(压缩、单个片段作为下拉列表或所有片段,如果它们可以水平放置)。然后调用 invalidateIntrinsicContentSize() 以重新计算内容大小。

  3. 使用在上一步中确定的模式,覆盖 intrinsicContentSize 并提供正确的大小(最小压缩版本或所有段都可以容纳的大小)。

通过这种方式,控件将所有这些功能包装到单个 NSView 子类中,并在调整窗口大小时减轻托管此控件的任何超级视图/窗口设置正确大小的责任。