Kevvv
Kevvv

Reputation: 4023

How to change the intrinsic content size through its priority

I want the second label (subview2) to stay intact and have the first label (subview1) shrink as the size of the container view decreases in size, but the second label is still shrink no matter how I adjust the priority:

import UIKit
import PlaygroundSupport

let containerView = UIView(frame: CGRect(origin: .zero, size: .init(width: 200, height: 300)))
let subview1 = UILabel()
subview1.text = "Hello"
subview1.backgroundColor = .red
containerView.addSubview(subview1)
subview1.translatesAutoresizingMaskIntoConstraints = false
let subview2 = UILabel()
subview2.text = "Hello"
subview2.backgroundColor = .cyan
containerView.addSubview(subview2)
subview2.translatesAutoresizingMaskIntoConstraints = false

let views: [String: Any] = ["subview1": subview1, "subview2": subview2]
let con1 = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(20)-[subview1]", metrics: nil, views: views)
let con2 = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(20)-[subview2]", metrics: nil, views: views)
let con3 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(30)-[subview1]", metrics: nil, views: views)
let con4 = NSLayoutConstraint.constraints(withVisualFormat: "H:[subview2]-(30)-|", metrics: nil, views: views)
let con5 = NSLayoutConstraint.constraints(withVisualFormat: "H:[subview1(>=100)]-(>=30)-[subview2(>=100)]", metrics: nil, views: views)
NSLayoutConstraint.activate(con1 + con2 + con3 + con4 + con5)

subview1.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: .horizontal)
subview2.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: .horizontal)

PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = containerView

enter image description here

I've tried increasing the priority of subview2 manually, but still didn't work:

let priority1 = subview2.contentCompressionResistancePriority(for: .horizontal)
print(priority1) // 750
let priority2 = subview2.contentCompressionResistancePriority(for: .horizontal)
print(priority2) // 750
subview2.setContentCompressionResistancePriority(priority2 + 100, for: .horizontal)

Upvotes: 0

Views: 1557

Answers (1)

DonMag
DonMag

Reputation: 77442

VFL (Visual Format Language) is very out-dated and limited. I strongly suggest you switch to more modern (and more flexible) syntax.

That said, the problem is with this line:

let con5 = NSLayoutConstraint.constraints(withVisualFormat: 
            "H:[subview1(>=100)]-(>=30)-[subview2(>=100)]", metrics: nil, views: views)

Your code is saying:

  • subview1 width must be greater-than-or-equal-to 100
  • subview2 width must be greater-than-or-equal-to 100

so, the setContentCompressionResistancePriority lines do nothing. You've already said "these are the required minimum widths."

If you change that line to:

let con5 = NSLayoutConstraint.constraints(withVisualFormat:
            "H:[subview1]-(>=30)-[subview2]", metrics: nil, views: views)

you should get your desired result.

Here, though, is your code using more modern constraint syntax -- you may find it much more logical and easier to understand:

NSLayoutConstraint.activate([

    // both labels 20-pts from containerView Top
    subview1.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 20.0),
    subview2.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 20.0),

    // left label Leading 30-pts from containerView Leading
    subview1.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 30.0),

    // right label Trailing 30-pts from containerView Trailing
    subview2.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -30.0),

    // right label Leading at least 30-pts from left label Trailing
    subview2.leadingAnchor.constraint(greaterThanOrEqualTo: subview1.trailingAnchor, constant: 30.0),

])

// don't do any of this...
//let views: [String: Any] = ["subview1": subview1, "subview2": subview2]
//let con1 = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(20)-[subview1]", metrics: nil, views: views)
//let con2 = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(20)-[subview2]", metrics: nil, views: views)
//let con3 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(30)-[subview1]", metrics: nil, views: views)
//let con4 = NSLayoutConstraint.constraints(withVisualFormat: "H:[subview2]-(30)-|", metrics: nil, views: views)
//let con5 = NSLayoutConstraint.constraints(withVisualFormat: "H:[subview1]-(>=30)-[subview2]", metrics: nil, views: views)
//NSLayoutConstraint.activate(con1 + con2 + con3 + con4 + con5)

Edit - to answer comment...

If you want the widths of your labels to each be >= 100, but allow them to compress (left-label compresses first), you need to add width constraints with Priority of less-than .required:

// both labels should have a width of >= 100
let leftWidth = subview1.widthAnchor.constraint(greaterThanOrEqualToConstant: 100.0)
let rightWidth = subview2.widthAnchor.constraint(greaterThanOrEqualToConstant: 100.0)

// set the Priority on the width constraints to less than required
//  to allow auto-layout to break that constraint if needed
leftWidth.priority = .defaultHigh
rightWidth.priority = .defaultHigh

// activate the width constraints
leftWidth.isActive = true
rightWidth.isActive = true

// tell leftLabel to compress before rightLabel
subview1.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: .horizontal)
subview2.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: .horizontal)

Upvotes: 1

Related Questions