Reputation: 813
I was trying a new StackView inside TableView's cell approach for certain requirement
Some Background - I have a TableView
and it's cell has a stackView
inside it. That stackView
can have multiple types of views inside it but for the sake of simplicity i am working with labels
for now. I have created a custom XIB(LabelInfoView
) which has 3 labels in horizontal. It shows data in sort of key-value pair combination. The labels can be multiline
Now I am adding 6 of these inside the stackView
which is present inside the tableView
cell
I have set content hugging and content compression resistance priorities
For label named Label
For label named Separator
For label named Info
Problem - The first time the table is loaded, some(not all) labels are getting truncated at random orders and even for random cells(say first cell's third label view is getting truncated and second cell's first and second and like that, totally random. No fixed orders even between multiple run of applications)
(Notice 5th labelView inside first tableView's cell)
Now when since i am dequeuing the cells, while scrolling, when my first cell go of screen and then when comes back on the screen, prepareForReuse()
will be called
cellsStackView.removeAllArrangedSubviews()
setupCard()
I am first removing all the subviews inside the stackView
and add them again . This time , after the method is called, the stackView is loaded correctly.
I have tried a lot of things like tweaking the priorities and stuff but nothing works so far!
Upvotes: 1
Views: 2463
Reputation: 813
Since I was setting numberOfLines
to 0 for both the leftLabel
and the rightLabel
, the auto-layout engine was not able to figure out that at which point the label should go multi-line i.e. the label needed a width constraint . So, I added the width of the leftLabel
to <= 50% of superview's width . And then it finally worked
Upvotes: 3
Reputation: 77690
Here is an example using code-only instead of a custom XIB.
No IBOutlet
or Prototype cells, so just assign WorkTableViewController
as the custom class for a UITableViewController
:
class MyThreeLabelView: UIView {
let leftLabel: UILabel = {
let v = UILabel()
return v
}()
let sepLabel: UILabel = {
let v = UILabel()
return v
}()
let rightLabel: UILabel = {
let v = UILabel()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
backgroundColor = .white
[leftLabel, sepLabel, rightLabel].forEach {
$0.font = UIFont.systemFont(ofSize: 16.0)
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
// bold-italic font for left label
leftLabel.font = leftLabel.font.boldItalic
// we want left and separator labels to NOT compress or expand
leftLabel.setContentHuggingPriority(.required, for: .horizontal)
leftLabel.setContentHuggingPriority(.required, for: .vertical)
leftLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
sepLabel.setContentHuggingPriority(.required, for: .horizontal)
sepLabel.setContentHuggingPriority(.required, for: .vertical)
sepLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
// right label should hug vertically
rightLabel.setContentHuggingPriority(.required, for: .vertical)
// right label can be mutliple lines
rightLabel.numberOfLines = 0
NSLayoutConstraint.activate([
// constrain all 3 labels 10-pts from top, at least 10-pts from bottom
leftLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10.0),
leftLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -10),
sepLabel.topAnchor.constraint(equalTo: leftLabel.topAnchor, constant: 0.0),
sepLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -10),
rightLabel.topAnchor.constraint(equalTo: leftLabel.topAnchor, constant: 0.0),
rightLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -10),
// constrain left label 10-pts from leading edge
leftLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10.0),
// constrain separator label 10-pts from left label
sepLabel.leadingAnchor.constraint(equalTo: leftLabel.trailingAnchor, constant: 10.0),
// constrain right label 10-pts from separator label
rightLabel.leadingAnchor.constraint(equalTo: sepLabel.trailingAnchor, constant: 10.0),
// constrain right label 10-pts from trailing edge
rightLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),
])
}
}
class StackCell: UITableViewCell {
let stackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.alignment = .fill
v.distribution = .fill
v.spacing = 0
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForReuse() {
super.prepareForReuse()
// remove all arrangedSubviews from stack view
stackView.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
}
func commonInit() -> Void {
contentView.backgroundColor = .lightGray
// add the stack view
contentView.addSubview(stackView)
// constrain 12-pts on all 4 sides
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12.0),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12.0),
])
}
func addLabels(_ labels: [String]) -> Void {
labels.forEach {
s in
// instance of MyThreeLabelView
let v = MyThreeLabelView()
// for this example, left and separator labels don't change
v.leftLabel.text = "Assigned To"
v.sepLabel.text = "-"
// assign right label text
v.rightLabel.text = s
// add MyThreeLabelView to the stack view
stackView.addArrangedSubview(v)
}
}
}
class WorkTableViewController: UITableViewController {
let reuseID = "StackCell"
var theData: [[String]] = [[String]]()
override func viewDidLoad() {
super.viewDidLoad()
// make 8 sets of 6-rows of labels
for i in 1...8 {
let tmp: [String] = [
"1) Row \(i)",
"2) Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s",
"3) Short text.",
"4) Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s",
"5) Short text.",
"6) Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s",
]
theData.append(tmp)
}
tableView.register(StackCell.self, forCellReuseIdentifier: reuseID)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return theData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) as! StackCell
cell.addLabels(theData[indexPath.row])
return cell
}
}
// UIFont extension for bold / italic / boldItalic
// found here: https://stackoverflow.com/a/21777132/6257435
extension UIFont {
var bold: UIFont {
return with(.traitBold)
} // bold
var italic: UIFont {
return with(.traitItalic)
} // italic
var boldItalic: UIFont {
return with([.traitBold, .traitItalic])
} // boldItalic
func with(_ traits: UIFontDescriptor.SymbolicTraits...) -> UIFont {
guard let descriptor = self.fontDescriptor.withSymbolicTraits(UIFontDescriptor.SymbolicTraits(traits).union(self.fontDescriptor.symbolicTraits)) else {
return self
}
return UIFont(descriptor: descriptor, size: 0)
}
func without(_ traits: UIFontDescriptor.SymbolicTraits...) -> UIFont {
guard let descriptor = self.fontDescriptor.withSymbolicTraits(self.fontDescriptor.symbolicTraits.subtracting(UIFontDescriptor.SymbolicTraits(traits))) else {
return self
}
return UIFont(descriptor: descriptor, size: 0)
}
} // extension
Result:
Upvotes: 1