1

I'm trying to programmatically create a stack view with a fill distribution that allows me to choose which subview stretches to fill the extra space, but so far I can't seem to control which view is expanding.

class ShelfVC: UIViewController {

    let shelfContentView = UIStackView()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(shelfContentView)
        shelfContentView.translatesAutoresizingMaskIntoConstraints = false
        shelfContentView.layoutEqualTo(view: view)
        shelfContentView.spacing = 16

        shelfContentView.isLayoutMarginsRelativeArrangement = true
        let shelfMargin = CGFloat(16)
        shelfContentView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: shelfMargin, leading: shelfMargin, bottom: shelfMargin, trailing: shelfMargin)
        
        createTestBlocks()
    }

    func createTestBlocks() {

            let view1 = UIView()
            shelfContentView.addArrangedSubview(view1)
            view1.widthAnchor.constraint(equalToConstant: 100).isActive = true
            view1.backgroundColor = .systemRed
            view1.setContentHuggingPriority(.defaultHigh, for: .horizontal)

            let view2 = UIView()
            shelfContentView.addArrangedSubview(view2)
            view2.widthAnchor.constraint(equalToConstant: 100).isActive = true
            view2.backgroundColor = .systemPurple
            view2.setContentHuggingPriority(.defaultLow, for: .horizontal)
            
            let view3 = UIView( )
            shelfContentView.addArrangedSubview(view3)
            view3.widthAnchor.constraint(equalToConstant: 100).isActive = true
            view3.backgroundColor = .systemBlue
            view3.setContentHuggingPriority(.defaultHigh, for: .horizontal)
    }
}

I'm expecting to see this:

enter image description here

But instead, I see this:

enter image description here

Why does the blue view stretch when it's hugging priority is higher than the purple view?

zakdances
  • 22,285
  • 32
  • 102
  • 173

2 Answers2

2

Content Hugging Priority is not the same as Constraint Priority, and Constraints do not define Intrinsic Content Size.

Content Hugging (and Content Compression Resistance) Priority is based on Intrinsic content size.

You've given each view a Width constraint, and your stack view is using that to begin the arrangement. It then breaks one of the constraints because it cannot satisfy all of them.

You have two options:

  • give the constraints different priorities
  • give the views an intrinsic content size and set Hugging priority

Here is the first example:

class ConstraintHuggingShelfVC: UIViewController {
    
    let shelfContentView = UIStackView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(shelfContentView)
        shelfContentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            
            shelfContentView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0),
            shelfContentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
            shelfContentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
            shelfContentView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
            
        ])
        
        //shelfContentView.layoutEqualTo(view: view)
        shelfContentView.spacing = 16
        
        shelfContentView.isLayoutMarginsRelativeArrangement = true
        let shelfMargin = CGFloat(16)
        shelfContentView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: shelfMargin, leading: shelfMargin, bottom: shelfMargin, trailing: shelfMargin)
        
        createTestBlocks()
    }
    
    func createTestBlocks() {
        
        let view1 = UIView()
        shelfContentView.addArrangedSubview(view1)
        view1.backgroundColor = .systemRed

        let wc1 = view1.widthAnchor.constraint(equalToConstant: 100)
        wc1.isActive = true
        wc1.priority = .defaultHigh

        let view2 = UIView()
        shelfContentView.addArrangedSubview(view2)
        view2.backgroundColor = .systemPurple

        let wc2 = view2.widthAnchor.constraint(equalToConstant: 100)
        wc2.isActive = true
        wc2.priority = .defaultLow

        let view3 = UIView( )
        shelfContentView.addArrangedSubview(view3)
        view3.backgroundColor = .systemBlue
        
        let wc3 = view3.widthAnchor.constraint(equalToConstant: 100)
        wc3.isActive = true
        wc3.priority = .defaultHigh
        
    }
}

and here's the second example:

class IntrinsicView: UIView {
    var myIntrinsicSize: CGSize = .zero
    override var intrinsicContentSize: CGSize {
        return myIntrinsicSize
    }
}

class IntrisicSizeShelfVC: UIViewController {
    
    let shelfContentView = UIStackView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(shelfContentView)
        shelfContentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            
            shelfContentView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0),
            shelfContentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
            shelfContentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
            shelfContentView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
            
        ])

//      shelfContentView.layoutEqualTo(view: view)
        shelfContentView.spacing = 16
        
        shelfContentView.isLayoutMarginsRelativeArrangement = true
        let shelfMargin = CGFloat(16)
        shelfContentView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: shelfMargin, leading: shelfMargin, bottom: shelfMargin, trailing: shelfMargin)
        
        createTestBlocks()
    }
    
    func createTestBlocks() {
        
        let view1 = IntrinsicView()
        shelfContentView.addArrangedSubview(view1)
        view1.backgroundColor = .systemRed

        view1.myIntrinsicSize = CGSize(width: 100, height: 0)
        view1.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        
        let view2 = IntrinsicView()
        shelfContentView.addArrangedSubview(view2)
        view2.backgroundColor = .systemPurple

        view2.myIntrinsicSize = CGSize(width: 100, height: 0)
        view2.setContentHuggingPriority(.defaultLow, for: .horizontal)
        
        let view3 = IntrinsicView( )
        shelfContentView.addArrangedSubview(view3)
        view3.backgroundColor = .systemBlue

        view3.myIntrinsicSize = CGSize(width: 100, height: 0)
        view3.setContentHuggingPriority(.defaultHigh, for: .horizontal)
    }
}

Both produce the identical layout:

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
0

UIStackView shrinks and expands using .fill based on the content compression priority if the arranged subviews don't fill the stack view. Thus, try changing setContentHuggingPriority to setContentCompressionResistancePriority.

See https://developer.apple.com/documentation/uikit/uistackview/distribution/fill for more information on the UIStackView.Distribution.fill property.

wriuhasdfhvhasdv
  • 474
  • 4
  • 20