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

Posted

技术标签:

【中文标题】根据最大的子视图使用自动布局调整超级视图的大小【英文标题】:Resize superview with autolayout depending on largest subview 【发布时间】:2015-07-14 07:41:16 【问题描述】:

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

例如。如果我们有两列布局,其中左列高度大于右列,则超级视图高度应与右列高度相同。比,如果我们将右列高度更改为大于左列高度,则超级视图高度应更改为右列的高度。如何做到这一点?

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

    最初我们有两列的布局,其中左侧NSView.Bottom 约束附加到超级视图的底部。

    如果我们按下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

【问题讨论】:

当视图高度改变时你会收到回调吗? 一种方法是为列视图注册NSNotificationCenter.defaultCenter().addObserver(self, selector: "viewDidResize:", name: NSViewFrameDidChangeNotification, object: self) 通知。比我可以手动计算和更改超级视图的高度。但也许可以仅通过约束重新创建这种行为? 【参考方案1】:

仅在约束条件下设法完成此任务。刚刚添加了列的容器视图并将其高度设置为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))

【讨论】:

【参考方案2】:

首先在superview上创建一个带高度的约束

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

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

[self layoutIfNeeded] 

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

【讨论】:

以上是关于根据最大的子视图使用自动布局调整超级视图的大小的主要内容,如果未能解决你的问题,请参考以下文章

根据子视图调整超级视图的大小

使用自动布局动态更改子视图后调整超级视图的大小

根据最高子视图按钮自动调整超级视图高度

带有子视图的自动布局 NSCollectionView

我们可以将具有自动布局的 XIB 加载为以编程方式创建的没有自动布局的视图的子视图,并使用父视图调整子视图的大小吗?

使用固定大小的子视图和自动布局调整 UIView 的大小不起作用