根据最大的子视图使用自动布局调整超级视图的大小

Resize superview with autolayout depending on largest subview

如果我们有具有动态高度的内部 NSView 列,使用自动布局调整超级视图大小的最佳做法是什么?

例如。如果我们有两列布局,其中左列高度大于右列,父视图高度应与右列高度相同。那么,如果我们将右列高度更改为大于左列高度,则超级视图高度应更改为右列高度。如何实现?

我做了示例项目来测试这个:

  1. 最初我们有两列的布局,其中 .BottomNSView 约束附加到父视图的底部。

  1. 如果我们按下 Make Right Bigger 按钮,我会让右边 NSView 的高度比左边的高。

所以我希望这里的 superview 根据更大的列(右列)改变高度。这样做有什么好的做法吗?

代码:

import Cocoa

class ViewController: NSViewController {

    let leftView = NSView()
    let rightView = NSView()
    let button = NSButton()

    var rightViewHeightConstraint: NSLayoutConstraint?

    override func loadView() {
        self.view = TestView()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        leftView.backgroundColor = NSColor.redColor()
        rightView.backgroundColor = NSColor.orangeColor()

        layoutLeft(view, insertView: leftView)
        layoutRight(view, insertView: rightView)

        button.title = "Make Right Bigger"
        button.target = self
        button.action = "makeBigger:"
        ViewControllerLayout.layoutBotton(view, insertView: button, bottom: -20)
    }

    func makeBigger(sender: AnyObject) {
        rightViewHeightConstraint?.animator().constant = 150.0
    }

    func layoutLeft(containerView: NSView, insertView: NSView) {
        insertView.translatesAutoresizingMaskIntoConstraints = false

        containerView.addSubview(insertView)

        let c1 = NSLayoutConstraint(item: insertView, attribute: .Left, relatedBy: .Equal, toItem: containerView, attribute: .Left, multiplier: 1.0, constant: 0.0)
        let c2 = NSLayoutConstraint(item: insertView, attribute: .Width, relatedBy: .Equal, toItem: containerView, attribute: .Width, multiplier: 0.5, constant: 0.0)
        let c3 = NSLayoutConstraint(item: insertView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100.0)
        let c4 = NSLayoutConstraint(item: insertView, attribute: .Top, relatedBy: .Equal, toItem: containerView, attribute: .Top, multiplier: 1.0, constant: 0.0)
        let c5 = NSLayoutConstraint(item: insertView, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1.0, constant: -60.0)

        containerView.addConstraint(c1)
        containerView.addConstraint(c2)
        containerView.addConstraint(c3)
        containerView.addConstraint(c4)
        containerView.addConstraint(c5)
    }

    func layoutRight(containerView: NSView, insertView: NSView) {
        insertView.translatesAutoresizingMaskIntoConstraints = false

        containerView.addSubview(insertView)

        let c1 = NSLayoutConstraint(item: insertView, attribute: .Right, relatedBy: .Equal, toItem: containerView, attribute: .Right, multiplier: 1.0, constant: 0.0)
        let c2 = NSLayoutConstraint(item: insertView, attribute: .Width, relatedBy: .Equal, toItem: containerView, attribute: .Width, multiplier: 0.5, constant: 0.0)
        let c3 = NSLayoutConstraint(item: insertView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50.0)
        let c4 = NSLayoutConstraint(item: insertView, attribute: .Top, relatedBy: .Equal, toItem: containerView, attribute: .Top, multiplier: 1.0, constant: 0.0)
        let c5 = NSLayoutConstraint(item: insertView, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1.0, constant: -60.0)

        containerView.addConstraint(c1)
        containerView.addConstraint(c2)
        containerView.addConstraint(c3)
        containerView.addConstraint(c4)
        // containerView.addConstraint(c5) // Cant add .Bottom constraint here, because of different column sizes.

        rightViewHeightConstraint = c3
    }
}

struct ViewControllerLayout {

    static func layoutBotton(containerView: NSView, insertView: NSView, bottom: Double) {
        insertView.translatesAutoresizingMaskIntoConstraints = false

        containerView.addSubview(insertView)

        containerView.addConstraint(NSLayoutConstraint(item: insertView, attribute: .CenterX, relatedBy: .Equal, toItem: containerView, attribute: .CenterX, multiplier: 1.0, constant: 0.0))
        containerView.addConstraint(NSLayoutConstraint(item: insertView, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1.0, constant: CGFloat(bottom)))
    }
}

下载测试项目:GitHub

首先创建一个高度为

的superview约束
let heightConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: greaterHeightAmongTwoColumns)

如评论中所述,您可以 post 通知,然后让超级视图知道更新的框架。超级视图会检查更新后的两列中的任何一列的高度是否大于其当前高度并更新其上面声明的heightConstraint,然后调用

[self layoutIfNeeded] 

在父视图的父视图上,以便它与新框架一起布局。

仅在限制条件下设法完成此任务。刚刚添加了列的容器视图,并将其高度设置为 GreaterThanOrEqual 到左列和右列。

view.addConstraint(NSLayoutConstraint(item: containerView, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: leftView, attribute: .Height, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: containerView, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: rightView, attribute: .Height, multiplier: 1, constant: 0))