Search code examples
iosautolayoutnslayoutconstraint

How to make auto-layout constraint dependent on multiple other anchors?


How can auto-layout be used to make a view's height equal to the sum of two other view's heights?

For example:

viewA.heightAnchor.constraint(equalTo: ...),
viewB.heightAnchor.constraint(equalTo: ...),
viewC.heightAnchor.constraint(equalTo: viewA.heightAnchor + viewB.heightAnchor)

Is there a solution that does not involve setting a constant value and recalculating it on every view bounds change?


Solution

  • You can, but only with the help of some helper views, I think. See this example playground I just now cooked up that achieves this:

    import Foundation
    import UIKit
    import PlaygroundSupport
    
    let vc = UIViewController()
    vc.view = UIView()
    vc.view.backgroundColor = .white
    
    let viewA = UIView()
    let viewB = UIView()
    let viewC = UIView()
    
    viewA.backgroundColor = .red
    viewB.backgroundColor = .green
    viewC.backgroundColor = .blue
    
    viewA.translatesAutoresizingMaskIntoConstraints = false
    viewB.translatesAutoresizingMaskIntoConstraints = false
    viewC.translatesAutoresizingMaskIntoConstraints = false
    
    vc.view.addSubview(viewA)
    vc.view.addSubview(viewB)
    vc.view.addSubview(viewC)
    
    let helperViewA = UIView()
    let helperViewB = UIView()
    
    helperViewA.translatesAutoresizingMaskIntoConstraints = false
    helperViewB.translatesAutoresizingMaskIntoConstraints = false
    
    helperViewA.isHidden = true
    helperViewB.isHidden = true
    
    vc.view.addSubview(helperViewA)
    vc.view.addSubview(helperViewB)
    
    viewA.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor).isActive = true
    viewA.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor).isActive = true
    viewA.trailingAnchor.constraint(equalTo: viewB.leadingAnchor).isActive = true
    viewA.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
    
    viewB.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor).isActive = true
    viewB.widthAnchor.constraint(equalTo: viewA.widthAnchor, multiplier: 1.0).isActive = true
    viewB.trailingAnchor.constraint(equalTo: vc.view.trailingAnchor).isActive = true
    viewB.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
    
    viewA.heightAnchor.constraint(equalTo: helperViewA.heightAnchor, multiplier: 1.0).isActive = true
    viewB.heightAnchor.constraint(equalTo: helperViewB.heightAnchor, multiplier: 1.0).isActive = true
    
    helperViewA.bottomAnchor.constraint(equalTo: helperViewB.topAnchor).isActive = true
    
    viewC.widthAnchor.constraint(equalToConstant: 100).isActive = true
    viewC.topAnchor.constraint(equalTo: helperViewA.topAnchor).isActive = true
    viewC.bottomAnchor.constraint(equalTo: helperViewB.bottomAnchor).isActive = true
    
    
    helperViewA.leadingAnchor.constraint(equalTo: viewC.leadingAnchor).isActive = true
    helperViewA.trailingAnchor.constraint(equalTo: viewC.trailingAnchor).isActive = true
    helperViewB.leadingAnchor.constraint(equalTo: viewC.leadingAnchor).isActive = true
    helperViewB.trailingAnchor.constraint(equalTo: viewC.trailingAnchor).isActive = true
    
    viewC.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true
    viewC.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor).isActive = true
    
    vc.view.frame.size = CGSize(width: 375, height: 667)
    PlaygroundPage.current.liveView = vc.view
    

    The idea is that you have two helper views stacked vertically on each other, connected by the top one's bottomAnchor and the bottom one's topAnchor. You then set each of these helper views' heights to be equal to your viewA and viewB heights. Then, your viewC can be attached to the top view's topAnchor and the bottom view's bottomAnchor, which gives the result of viewC being the height of viewA's height plus viewB's height.