Home > Blockchain >  Content Hugging Priority With UIImageView
Content Hugging Priority With UIImageView

Time:11-28

I am laying out a similar UIStackView to Twitter's author layout as per below;

twitter screenshot

However, I am not getting the desired effect from setting the various priorities;

setContentHuggingPriority(UILayoutPriority(rawValue: 1), for: .horizontal)

The UIStackView contains the below items;

  1. Display Name UILabel
  2. Verified Badge UIImageView
  3. Handle UILabel
  4. Timestamp UILabel

The functionality I am looking for is the below;

  1. The Timestamp and Verified Badge must not shrink at any time.
  2. The Timestamp must take up the remaining space if the UIStackView doesn't fill the width
  3. The Handle will shrink first
  4. The Display Name will shrink last

Any help is greatly appreciated.

CodePudding user response:

You can follow your "functionality I am looking for" list as you've written it...

  1. The Timestamp and Verified Badge must not shrink at any time.

     // don't let Timestamp compress
     timeStampLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
    
     // don't let "Dot" compress
     dotLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
    
     // badge image view is square (1:1 ratio)
     //  Width Anchor prevents both compression and expansion
     badgeImageView.widthAnchor.constraint(equalTo: badgeImageView.heightAnchor).isActive = true
    
  2. The Timestamp must take up the remaining space if the UIStackView doesn't fill the width

     // don't let Display Name, Handle or Dot expand Horizontally
     displayNameLabel.setContentHuggingPriority(.required, for: .horizontal)
     handleLabel.setContentHuggingPriority(.required, for: .horizontal)
     dotLabel.setContentHuggingPriority(.required, for: .horizontal)
    
  3. The Handle will shrink first

     // Handle shrink first
     handleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    
  4. The Display Name will shrink last

     // Display Name shrink next
     displayNameLabel.setContentCompressionResistancePriority(.defaultLow   1, for: .horizontal)
    
  5. You didn't specify, so...

     // Handle *could* to shrink to "no width"
     //  so use a min-Width to show at least a char or two
     // if you want to allow it to disappear, comment out this line
     handleLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 24.0).isActive = true
    

Here's a complete example -- it has two buttons... one to cycle through some sample data, and one to toggle background colors on the labels to see their frames. The stackView is added to the main view, but it will work the same when used in (what I assume is) your cell:

class TwitLineVC: UIViewController {

    let displayNameLabel = UILabel()
    let badgeImageView = UIImageView()
    let handleLabel = UILabel()
    let dotLabel = UILabel()
    let timeStampLabel = UILabel()
    
    let sampleData: [[String]] = [
        ["Stack Overflow", "@StackOverflow", "Nov 13"],
        ["Longer Display Name", "@LongerHandle", "Nov 14"],
        ["Much Longer Display Name", "@ThisHandleWillCompress", "Nov 15"],
        ["Much Longer Display Name Will Also Compress", "@ThisHandleWillCompress", "Nov 16"],
    ]
    var idx: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .black
        
        guard let img = UIImage(named: "twcheck") else {
            fatalError("Could not load image!")
        }
        badgeImageView.image = img
        
        let stackView = UIStackView()
        stackView.spacing = 4
        
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
        ])

        // don't let Display Name, Handle or Dot expand Horizontally
        displayNameLabel.setContentHuggingPriority(.required, for: .horizontal)
        handleLabel.setContentHuggingPriority(.required, for: .horizontal)
        dotLabel.setContentHuggingPriority(.required, for: .horizontal)
        
        // don't let Timestamp compress
        timeStampLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
        
        // don't let "Dot" compress
        dotLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
        
        // badge image view is square (1:1 ratio)
        //  Width Anchor prevents both compression and expansion
        badgeImageView.widthAnchor.constraint(equalTo: badgeImageView.heightAnchor).isActive = true
        
        // Handle shrink first
        handleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        
        // Display Name shrink next
        displayNameLabel.setContentCompressionResistancePriority(.defaultLow   1, for: .horizontal)
        
        // Handle *could* to shrink to "no width"
        //  so use a min-Width to show at least a char or two
        // if you want to allow it to disappear, comment out this line
        handleLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 24.0).isActive = true
        
        // use Display Name label height to control stack view height
        displayNameLabel.setContentHuggingPriority(.required, for: .vertical)
        
        // add to stack view
        stackView.addArrangedSubview(displayNameLabel)
        stackView.addArrangedSubview(badgeImageView)
        stackView.addArrangedSubview(handleLabel)
        stackView.addArrangedSubview(dotLabel)
        stackView.addArrangedSubview(timeStampLabel)
        
        displayNameLabel.textColor = .white
        handleLabel.textColor = .lightGray
        dotLabel.textColor = .lightGray
        timeStampLabel.textColor = .lightGray

        let fSize: CGFloat = 12.0
        displayNameLabel.font = .systemFont(ofSize: fSize, weight: .bold)
        handleLabel.font = .systemFont(ofSize: fSize, weight: .regular)
        dotLabel.font = handleLabel.font
        timeStampLabel.font = handleLabel.font

        // this never changes
        dotLabel.text = "•"

        // a button to cycle through sample data
        let btn1 = UIButton(type: .system)
        btn1.setTitle("Next Data Set", for: [])
        btn1.addTarget(self, action: #selector(updateData(_:)), for: .touchUpInside)
        btn1.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(btn1)
        
        // a button to toggle background colors
        let btn2 = UIButton(type: .system)
        btn2.setTitle("Toggle Colors", for: [])
        btn2.addTarget(self, action: #selector(toggleColors(_:)), for: .touchUpInside)
        btn2.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(btn2)
        
        NSLayoutConstraint.activate([
            btn1.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 40.0),
            btn1.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            btn2.topAnchor.constraint(equalTo: btn1.bottomAnchor, constant: 20.0),
            btn2.centerXAnchor.constraint(equalTo: g.centerXAnchor),
        ])
        
        // fill with the first data set
        updateData(nil)

    }
    
    @objc func updateData(_ sender: Any?) {

        displayNameLabel.text = sampleData[idx % sampleData.count][0]
        handleLabel.text = sampleData[idx % sampleData.count][1]
        timeStampLabel.text = sampleData[idx % sampleData.count][2]

        idx  = 1
        
    }
    @objc func toggleColors(_ sender: Any?) {

        if displayNameLabel.backgroundColor == .clear {
            displayNameLabel.backgroundColor = .systemGreen
            handleLabel.backgroundColor = .systemBlue
            dotLabel.backgroundColor = .systemYellow
            timeStampLabel.backgroundColor = .systemRed
        } else {
            displayNameLabel.backgroundColor = .clear
            handleLabel.backgroundColor = .clear
            dotLabel.backgroundColor = .clear
            timeStampLabel.backgroundColor = .clear
        }
        
    }
}
  • Related