I am laying out a similar UIStackView
to Twitter's author layout as per below;
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;
- Display Name
UILabel
- Verified Badge
UIImageView
- Handle
UILabel
- Timestamp
UILabel
The functionality I am looking for is the below;
- The Timestamp and Verified Badge must not shrink at any time.
- The Timestamp must take up the remaining space if the
UIStackView
doesn't fill the width - The Handle will shrink first
- 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...
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
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)
The Handle will shrink first
// Handle shrink first handleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
The Display Name will shrink last
// Display Name shrink next displayNameLabel.setContentCompressionResistancePriority(.defaultLow 1, for: .horizontal)
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
}
}
}