如何正确获取 tableViewCell 的 contentView 绑定大小?
How to correctly get tableViewCell's contentView bound size?
资源:
我已经阅读了来自 Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
的多个答案
并遵循了他们的建议,但没有用。
重现设置:
如果您 copy/paste MyTableViewCell
和 ViewController
片段:那么您可以重现该问题。
我已将 MyTableViewCell 子类化并添加了我自己的标签。
import UIKit
class MyTableViewCell: UITableViewCell {
lazy var customLabel : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.numberOfLines = 0
return lbl
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupLayout()
}
private func setupLayout(){
contentView.addSubview(customLabel)
let top = customLabel.topAnchor.constraint(equalTo: contentView.topAnchor)
let bottom = customLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
let leadingFromImage = customLabel.leadingAnchor.constraint(equalTo: imageView!.trailingAnchor, constant: 5)
let trailing = customLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
NSLayoutConstraint.activate([top, bottom, leadingFromImage, trailing])
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
以下 ViewController
包含我的表格视图:
import UIKit
class ViewController: UIViewController {
var datasource = ["It would have been a great day had Manchester United Lost its \n game. Anyhow I hope tomorrow Arsenal will win the game"]
lazy var tableView : UITableView = {
let table = UITableView()
table.delegate = self
table.dataSource = self
table.translatesAutoresizingMaskIntoConstraints = false
table.estimatedRowHeight = 100
table.rowHeight = UITableViewAutomaticDimension
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.pinToAllEdges(of: view)
tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "id")
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell
cell.customLabel.text = datasource[indexPath.row]
logInfo(of: cell)
cell.accessoryType = .detailDisclosureButton
cell.imageView?.image = UIImage(named: "honey")
cell.layoutSubviews()
cell.customLabel.preferredMaxLayoutWidth = tableView.bounds.width
logInfo(of: cell)
print("---------")
return cell
}
private func logInfo(of cell: MyTableViewCell){
print("boundsWidth: \(cell.contentView.bounds.width) | maxLayoutWidth: \(cell.contentView.bounds.width - 44 - 15 - 5) | systemLayoutSizeFitting : \(cell.customLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize))")
}
}
extension UIView{
func pinToAllEdges(of view: UIView){
let leading = leadingAnchor.constraint(equalTo: view.leadingAnchor)
let top = topAnchor.constraint(equalTo: view.topAnchor)
let trailing = trailingAnchor.constraint(equalTo: view.trailingAnchor)
let bottom = bottomAnchor.constraint(equalTo: view.bottomAnchor)
NSLayoutConstraint.activate([leading, top, trailing, bottom])
}
}
Link for honey image 我用过。我已将其大小设置为 44 * 44
主要问题
我的主要问题在里面 cellForRowAtIndex
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell
cell.customLabel.text = datasource[indexPath.row]
logInfo(of: cell)
cell.accessoryType = .detailDisclosureButton
cell.imageView?.image = UIImage(named: "honey")
cell.layoutSubviews()
cell.customLabel.preferredMaxLayoutWidth = cell.contentView.bounds.width
logInfo(of: cell)
print("---------")
return cell
}
问题:
无论出于何种原因赋值给:
cell.customLabel.preferredMaxLayoutWidth
好像不太对。
Q1:这是为什么?
Q2: 我在调用 cell.layoutSubviews
之前和之后记录了 contentView 的绑定,它从 320
切换到 260
但是然后最终在 viewDebugger 中显示为 308
!!!
为什么contenView的边界又变了?!
我从问题中删除了一些其他屏幕截图。它们大多杂乱无章,但也许值得一看。大家可以看看修改历史
我认为问题与使用默认单元格的 imageView
.
有关
图像视图本身在其 .image
属性 设置之前不存在,因此在您的单元格初始化中,您将自定义标签限制为图像视图 0,0, 0,0
然后,在 cellForRowAt
中,您设置 .image
属性,它 出现 那个动作 也设置contentView的高度。我找不到关于它的任何文档,并且在调试中挖掘我找不到任何冲突的约束,所以我不完全确定为什么会这样。
两个选项:
1 - 不创建和添加自定义标签,而是将默认 .textLabel
上的 .numberOfLines
设置为 0
。 应该就够了。
2 - 如果您需要自定义标签,还 添加自定义图像视图。
选项 2 在这里:
class MyTableViewCell: UITableViewCell {
lazy var customLabel : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.numberOfLines = 0
lbl.setContentCompressionResistancePriority(.required, for: .vertical)
return lbl
}()
lazy var customImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupLayout()
}
private func setupLayout(){
contentView.addSubview(customLabel)
contentView.addSubview(customImageView)
// constrain leading of imageView to be 15-pts from the leading of the contentView
let imgViewLeading = customImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15)
// constrain width of imageView to 42-pts
let imgViewWidth = customImageView.widthAnchor.constraint(equalToConstant: 42)
// constrain height of imageView to be equal to width of imageView
let imgViewHeight = customImageView.heightAnchor.constraint(equalTo: customImageView.widthAnchor, multiplier: 1.0)
// center imageView vertically
let imgViewCenterY = customImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0.0)
// top and bottom constraints for the imageView also need to be set,
// otherwise the image will exceed the height of the cell when there
// is not enough text to wrap and expand the height of the label
// constrain top of imageView to be *at least* 4-pts from the top of the cell
let imgViewTop = customImageView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 4)
// constrain bottom of imageView to be *at least* 4-pts from the bottom of the cell
let imgViewBottom = customImageView.topAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -4)
// constrain top of the label to be *at least* 4-pts from the top of the cell
let top = customLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4)
// if you want the text in the label vertically centered in the cell
// constrain bottom of the label to be *exactly* 4-pts from the bottom of the cell
let bottom = customLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4)
// if you want the text in the label top-aligned in the cell
// constrain bottom of the label to be *at least* 4-pts from the bottom of the cell
// let bottom = customLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -4)
// constrain leading of the label to be 5-pts from the trailing of the image
let leadingFromImage = customLabel.leadingAnchor.constraint(equalTo: customImageView.trailingAnchor, constant: 5)
// constrain the trailing of the label to the trailing of the contentView
let trailing = customLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
NSLayoutConstraint.activate([
top, bottom, leadingFromImage, trailing,
imgViewLeading, imgViewCenterY, imgViewWidth, imgViewHeight,
imgViewTop, imgViewBottom
])
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
class HoneyViewController: UIViewController {
var datasource = [
"It would have been a great day had Manchester United Lost its game. Anyhow I hope tomorrow Arsenal will win the game",
"One line.",
"Two\nLines.",
]
lazy var tableView : UITableView = {
let table = UITableView()
table.delegate = self
table.dataSource = self
table.translatesAutoresizingMaskIntoConstraints = false
table.estimatedRowHeight = 100
table.rowHeight = UITableViewAutomaticDimension
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.pinToAllEdges(of: view)
tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "id")
}
}
extension HoneyViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell
cell.customLabel.text = datasource[indexPath.row]
logInfo(of: cell)
cell.accessoryType = .detailDisclosureButton
cell.customImageView.image = UIImage(named: "Honey")
logInfo(of: cell)
print("---------")
return cell
}
private func logInfo(of cell: MyTableViewCell){
print("boundsWidth: \(cell.contentView.bounds.width) | maxLayoutWidth: \(cell.contentView.bounds.width - 44 - 15 - 5) | systemLayoutSizeFitting : \(cell.customLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize))")
}
}
extension UIView{
func pinToAllEdges(of view: UIView){
let leading = leadingAnchor.constraint(equalTo: view.leadingAnchor)
let top = topAnchor.constraint(equalTo: view.topAnchor)
let trailing = trailingAnchor.constraint(equalTo: view.trailingAnchor)
let bottom = bottomAnchor.constraint(equalTo: view.bottomAnchor)
NSLayoutConstraint.activate([leading, top, trailing, bottom])
}
}
编辑:
还需要一些约束。如果单元格只有一行的文本(没有换行),imageView 高度将超过单元格的高度:
因此,我们向 imageView 添加顶部和底部约束以适应 至少 单元格的顶部和底部:
而且,加上一些填充可能看起来会好一些,所以我们将 imageView 的顶部和底部限制为距顶部和底部至少 4 磅单元格的:
如果需要,我们还可以 "top-align" 标签中的文本,方法是将其底部限制为距底部至少 4 磅,而不是 正好 从底部算起 4 分:
我编辑的代码中的注释应该解释每一个差异。
资源:
我已经阅读了来自 Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
的多个答案并遵循了他们的建议,但没有用。
重现设置:
如果您 copy/paste MyTableViewCell
和 ViewController
片段:那么您可以重现该问题。
我已将 MyTableViewCell 子类化并添加了我自己的标签。
import UIKit
class MyTableViewCell: UITableViewCell {
lazy var customLabel : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.numberOfLines = 0
return lbl
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupLayout()
}
private func setupLayout(){
contentView.addSubview(customLabel)
let top = customLabel.topAnchor.constraint(equalTo: contentView.topAnchor)
let bottom = customLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
let leadingFromImage = customLabel.leadingAnchor.constraint(equalTo: imageView!.trailingAnchor, constant: 5)
let trailing = customLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
NSLayoutConstraint.activate([top, bottom, leadingFromImage, trailing])
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
以下 ViewController
包含我的表格视图:
import UIKit
class ViewController: UIViewController {
var datasource = ["It would have been a great day had Manchester United Lost its \n game. Anyhow I hope tomorrow Arsenal will win the game"]
lazy var tableView : UITableView = {
let table = UITableView()
table.delegate = self
table.dataSource = self
table.translatesAutoresizingMaskIntoConstraints = false
table.estimatedRowHeight = 100
table.rowHeight = UITableViewAutomaticDimension
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.pinToAllEdges(of: view)
tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "id")
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell
cell.customLabel.text = datasource[indexPath.row]
logInfo(of: cell)
cell.accessoryType = .detailDisclosureButton
cell.imageView?.image = UIImage(named: "honey")
cell.layoutSubviews()
cell.customLabel.preferredMaxLayoutWidth = tableView.bounds.width
logInfo(of: cell)
print("---------")
return cell
}
private func logInfo(of cell: MyTableViewCell){
print("boundsWidth: \(cell.contentView.bounds.width) | maxLayoutWidth: \(cell.contentView.bounds.width - 44 - 15 - 5) | systemLayoutSizeFitting : \(cell.customLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize))")
}
}
extension UIView{
func pinToAllEdges(of view: UIView){
let leading = leadingAnchor.constraint(equalTo: view.leadingAnchor)
let top = topAnchor.constraint(equalTo: view.topAnchor)
let trailing = trailingAnchor.constraint(equalTo: view.trailingAnchor)
let bottom = bottomAnchor.constraint(equalTo: view.bottomAnchor)
NSLayoutConstraint.activate([leading, top, trailing, bottom])
}
}
Link for honey image 我用过。我已将其大小设置为 44 * 44
主要问题
我的主要问题在里面 cellForRowAtIndex
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell
cell.customLabel.text = datasource[indexPath.row]
logInfo(of: cell)
cell.accessoryType = .detailDisclosureButton
cell.imageView?.image = UIImage(named: "honey")
cell.layoutSubviews()
cell.customLabel.preferredMaxLayoutWidth = cell.contentView.bounds.width
logInfo(of: cell)
print("---------")
return cell
}
问题:
无论出于何种原因赋值给:
cell.customLabel.preferredMaxLayoutWidth
好像不太对。
Q1:这是为什么?
Q2: 我在调用 cell.layoutSubviews
之前和之后记录了 contentView 的绑定,它从 320
切换到 260
但是然后最终在 viewDebugger 中显示为 308
!!!
为什么contenView的边界又变了?!
我从问题中删除了一些其他屏幕截图。它们大多杂乱无章,但也许值得一看。大家可以看看修改历史
我认为问题与使用默认单元格的 imageView
.
图像视图本身在其 .image
属性 设置之前不存在,因此在您的单元格初始化中,您将自定义标签限制为图像视图 0,0, 0,0
然后,在 cellForRowAt
中,您设置 .image
属性,它 出现 那个动作 也设置contentView的高度。我找不到关于它的任何文档,并且在调试中挖掘我找不到任何冲突的约束,所以我不完全确定为什么会这样。
两个选项:
1 - 不创建和添加自定义标签,而是将默认 .textLabel
上的 .numberOfLines
设置为 0
。 应该就够了。
2 - 如果您需要自定义标签,还 添加自定义图像视图。
选项 2 在这里:
class MyTableViewCell: UITableViewCell {
lazy var customLabel : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.numberOfLines = 0
lbl.setContentCompressionResistancePriority(.required, for: .vertical)
return lbl
}()
lazy var customImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupLayout()
}
private func setupLayout(){
contentView.addSubview(customLabel)
contentView.addSubview(customImageView)
// constrain leading of imageView to be 15-pts from the leading of the contentView
let imgViewLeading = customImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15)
// constrain width of imageView to 42-pts
let imgViewWidth = customImageView.widthAnchor.constraint(equalToConstant: 42)
// constrain height of imageView to be equal to width of imageView
let imgViewHeight = customImageView.heightAnchor.constraint(equalTo: customImageView.widthAnchor, multiplier: 1.0)
// center imageView vertically
let imgViewCenterY = customImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0.0)
// top and bottom constraints for the imageView also need to be set,
// otherwise the image will exceed the height of the cell when there
// is not enough text to wrap and expand the height of the label
// constrain top of imageView to be *at least* 4-pts from the top of the cell
let imgViewTop = customImageView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 4)
// constrain bottom of imageView to be *at least* 4-pts from the bottom of the cell
let imgViewBottom = customImageView.topAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -4)
// constrain top of the label to be *at least* 4-pts from the top of the cell
let top = customLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4)
// if you want the text in the label vertically centered in the cell
// constrain bottom of the label to be *exactly* 4-pts from the bottom of the cell
let bottom = customLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4)
// if you want the text in the label top-aligned in the cell
// constrain bottom of the label to be *at least* 4-pts from the bottom of the cell
// let bottom = customLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -4)
// constrain leading of the label to be 5-pts from the trailing of the image
let leadingFromImage = customLabel.leadingAnchor.constraint(equalTo: customImageView.trailingAnchor, constant: 5)
// constrain the trailing of the label to the trailing of the contentView
let trailing = customLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
NSLayoutConstraint.activate([
top, bottom, leadingFromImage, trailing,
imgViewLeading, imgViewCenterY, imgViewWidth, imgViewHeight,
imgViewTop, imgViewBottom
])
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
class HoneyViewController: UIViewController {
var datasource = [
"It would have been a great day had Manchester United Lost its game. Anyhow I hope tomorrow Arsenal will win the game",
"One line.",
"Two\nLines.",
]
lazy var tableView : UITableView = {
let table = UITableView()
table.delegate = self
table.dataSource = self
table.translatesAutoresizingMaskIntoConstraints = false
table.estimatedRowHeight = 100
table.rowHeight = UITableViewAutomaticDimension
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.pinToAllEdges(of: view)
tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "id")
}
}
extension HoneyViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) as! MyTableViewCell
cell.customLabel.text = datasource[indexPath.row]
logInfo(of: cell)
cell.accessoryType = .detailDisclosureButton
cell.customImageView.image = UIImage(named: "Honey")
logInfo(of: cell)
print("---------")
return cell
}
private func logInfo(of cell: MyTableViewCell){
print("boundsWidth: \(cell.contentView.bounds.width) | maxLayoutWidth: \(cell.contentView.bounds.width - 44 - 15 - 5) | systemLayoutSizeFitting : \(cell.customLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize))")
}
}
extension UIView{
func pinToAllEdges(of view: UIView){
let leading = leadingAnchor.constraint(equalTo: view.leadingAnchor)
let top = topAnchor.constraint(equalTo: view.topAnchor)
let trailing = trailingAnchor.constraint(equalTo: view.trailingAnchor)
let bottom = bottomAnchor.constraint(equalTo: view.bottomAnchor)
NSLayoutConstraint.activate([leading, top, trailing, bottom])
}
}
编辑:
还需要一些约束。如果单元格只有一行的文本(没有换行),imageView 高度将超过单元格的高度:
因此,我们向 imageView 添加顶部和底部约束以适应 至少 单元格的顶部和底部:
而且,加上一些填充可能看起来会好一些,所以我们将 imageView 的顶部和底部限制为距顶部和底部至少 4 磅单元格的:
如果需要,我们还可以 "top-align" 标签中的文本,方法是将其底部限制为距底部至少 4 磅,而不是 正好 从底部算起 4 分:
我编辑的代码中的注释应该解释每一个差异。