StackView 中的几个标签最初被截断但在滚动时完美显示
Few labels inside StackView truncated initially but appears perfectly upon scrolling
我正在尝试在 TableView 的单元格方法中使用新的 StackView 来满足某些要求
一些背景 - 我有一个 TableView
,它的单元格里面有一个 stackView
。 stackView
内部可以有多种类型的视图,但为了简单起见,我现在使用 labels
。我创建了一个自定义 XIB(LabelInfoView
),它有 3 个水平标签。它以键值对组合的形式显示数据。标签可以是多行
现在我在 stackView
中添加了其中的 6 个,它存在于 tableView
单元格中
我已经设置了 content hugging 和 content compression resistance 优先级
对于名为 Label
的标签
对于名为 Separator
的标签
对于名为 Info
的标签
问题 - 第一次加载 table 时,一些(不是全部)标签被 t运行 随机排序,甚至随机单元格(假设第一个单元格的第三个标签视图得到 t运行 分类,第二个单元格的第一个和第二个标签视图完全随机。即使在多个 运行 应用程序之间也没有固定顺序)
(注意第一个 tableView 的单元格中的第 5 个 labelView)
现在,由于我正在使单元格出队,在滚动时,当我的第一个单元格离开屏幕然后返回屏幕时,prepareForReuse()
将被调用
cellsStackView.removeAllArrangedSubviews()
setupCard()
我首先删除 stackView
中的所有子视图,然后再次添加它们。这次调用方法后,正确加载了stackView。
我已经尝试了很多方法,比如调整优先级等等,但到目前为止没有任何效果!
这是一个使用纯代码而不是自定义 XIB 的示例。
没有 IBOutlet
或原型单元格,因此只需将 WorkTableViewController
指定为 UITableViewController
的自定义 class:
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 {
[=10=].font = UIFont.systemFont(ofSize: 16.0)
[=10=].translatesAutoresizingMaskIntoConstraints = false
addSubview([=10=])
}
// 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 {
[=10=].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:
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
结果:
由于我将 numberOfLines
设置为 0 leftLabel
和 rightLabel
,自动布局引擎无法弄清楚标签应该在什么时候变成多行,即标签需要 width constraint 。因此,我将 leftLabel
的宽度添加到 <= superview 的宽度 的 50%。然后终于成功了
我正在尝试在 TableView 的单元格方法中使用新的 StackView 来满足某些要求
一些背景 - 我有一个 TableView
,它的单元格里面有一个 stackView
。 stackView
内部可以有多种类型的视图,但为了简单起见,我现在使用 labels
。我创建了一个自定义 XIB(LabelInfoView
),它有 3 个水平标签。它以键值对组合的形式显示数据。标签可以是多行
现在我在 stackView
中添加了其中的 6 个,它存在于 tableView
单元格中
我已经设置了 content hugging 和 content compression resistance 优先级
对于名为 Label
的标签对于名为 Separator
的标签对于名为 Info
的标签问题 - 第一次加载 table 时,一些(不是全部)标签被 t运行 随机排序,甚至随机单元格(假设第一个单元格的第三个标签视图得到 t运行 分类,第二个单元格的第一个和第二个标签视图完全随机。即使在多个 运行 应用程序之间也没有固定顺序)
(注意第一个 tableView 的单元格中的第 5 个 labelView)
现在,由于我正在使单元格出队,在滚动时,当我的第一个单元格离开屏幕然后返回屏幕时,prepareForReuse()
将被调用
cellsStackView.removeAllArrangedSubviews()
setupCard()
我首先删除 stackView
中的所有子视图,然后再次添加它们。这次调用方法后,正确加载了stackView。
我已经尝试了很多方法,比如调整优先级等等,但到目前为止没有任何效果!
这是一个使用纯代码而不是自定义 XIB 的示例。
没有 IBOutlet
或原型单元格,因此只需将 WorkTableViewController
指定为 UITableViewController
的自定义 class:
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 {
[=10=].font = UIFont.systemFont(ofSize: 16.0)
[=10=].translatesAutoresizingMaskIntoConstraints = false
addSubview([=10=])
}
// 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 {
[=10=].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:
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
结果:
由于我将 numberOfLines
设置为 0 leftLabel
和 rightLabel
,自动布局引擎无法弄清楚标签应该在什么时候变成多行,即标签需要 width constraint 。因此,我将 leftLabel
的宽度添加到 <= superview 的宽度 的 50%。然后终于成功了