UITableViewCell 的异步高度更改
Async height change for UITableViewCell
在我的一个项目中,我需要根据图像大小更改 UITableViewCell 中 UIImageView 的高度,但问题是有时我必须在单元格显示后才这样做。
所以,如果我事先知道所有图像尺寸,我当前的解决方案就像一个魅力,但如果我试图延迟计算它 – 它完全坏了(尤其是滚动但即使没有它也坏了) .
我制作了示例项目来说明这一点。没有异步下载,但我试图在一些延迟(1 秒)后动态更改 UIImageView 的高度。高度取决于 UIImageView,因此每个下一个 UIImageView 都应该比前一个略高(10 像素)。另外,我有一个 UILabel,仅限于 UIImageView。
看起来像那样(UIImageViews 是红色的)
如果我尝试执行此异步操作,它看起来像这样,所有 UILabel 在这里都被破坏了。
这是滚动之后的一个(也是异步的):
我在这里做错了什么?我已经阅读了几个关于动态高度的帖子,但是 none 的解决方案对我有用。
我的代码相当简单:
func addTableView() {
tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = .black
tableView.register(DynamicCell.self, forCellReuseIdentifier: "dynamicCell")
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamicCell", for: indexPath) as! DynamicCell
cell.message = messageArray[indexPath.row]
cell.backgroundColor = .clear
cell.selectionStyle = .none
cell.buildCell()
return cell
}
DynamicCell.swift(代表现在什么都不做):
var backView: UIView!
var label: UILabel!
var picView: UIImageView!
var message: DMessage?
var picViewHeight: NSLayoutConstraint!
var delegate: RefreshCellDelegate?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backView = UIView()
backView.translatesAutoresizingMaskIntoConstraints = false
backView.backgroundColor = .white
backView.clipsToBounds = true
backView.layer.cornerRadius = 8.0
self.addSubview(backView)
label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .left
label.textColor = .black
label.numberOfLines = 0
backView.addSubview(label)
picView = UIImageView()
picView.translatesAutoresizingMaskIntoConstraints = false
picView.clipsToBounds = true
picView.backgroundColor = .red
backView.addSubview(picView)
addMainConstraints()
}
func addMainConstraints() {
backView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8).isActive = true
backView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -32).isActive = true
backView.topAnchor.constraint(equalTo: self.topAnchor, constant: 4).isActive = true
backView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -4).isActive = true
picView.topAnchor.constraint(equalTo: backView.topAnchor, constant: 0).isActive = true
picView.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 0).isActive = true
picView.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: 0).isActive = true
label.topAnchor.constraint(equalTo: picView.bottomAnchor, constant: 0).isActive = true
label.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 8).isActive = true
label.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -8).isActive = true
label.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -4).isActive = true
picViewHeight = NSLayoutConstraint(item: picView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
picViewHeight.priority = UILayoutPriority(999)
picViewHeight.isActive = true
}
override func prepareForReuse() {
picViewHeight.constant = 0
//picViewHeight.constant = 0
}
func buildCell() {
guard let message = message else {return}
label.attributedText = NSAttributedString(string: message.text)
changeHeightWithDelay()
//changeHeightWithoutDelay()
}
func changeHeightWithoutDelay() {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
func changeHeightWithDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
}
将此作为答案。
我注意到一件事,当你在玩 cell 时,最好使用 contentView 而不是直接使用 self。即 self.contentView.addSubview()。 refreshcell 函数有什么作用?您是否尝试过将其标记为 needsSetDisplay,以便在下一个绘制周期中更新它?您试过调用 layoutIfNeeded 吗?
进一步解释一下,您的视图已经 'rendered' 当您想更改视图的 height/width 时,您需要通知它有更新。当您将视图标记为 setNeedsDisplay
并且在下一个渲染周期中它将被更新时会发生这种情况
有关苹果文档的更多信息here
You can use this method or the setNeedsDisplay(_:) to notify the system that your view’s contents need to be redrawn. This method makes a note of the request and returns immediately. The view is not actually redrawn until the next drawing cycle, at which point all invalidated views are updated.
在我的一个项目中,我需要根据图像大小更改 UITableViewCell 中 UIImageView 的高度,但问题是有时我必须在单元格显示后才这样做。
所以,如果我事先知道所有图像尺寸,我当前的解决方案就像一个魅力,但如果我试图延迟计算它 – 它完全坏了(尤其是滚动但即使没有它也坏了) .
我制作了示例项目来说明这一点。没有异步下载,但我试图在一些延迟(1 秒)后动态更改 UIImageView 的高度。高度取决于 UIImageView,因此每个下一个 UIImageView 都应该比前一个略高(10 像素)。另外,我有一个 UILabel,仅限于 UIImageView。
看起来像那样(UIImageViews 是红色的)
如果我尝试执行此异步操作,它看起来像这样,所有 UILabel 在这里都被破坏了。
这是滚动之后的一个(也是异步的):
我在这里做错了什么?我已经阅读了几个关于动态高度的帖子,但是 none 的解决方案对我有用。
我的代码相当简单:
func addTableView() {
tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = .black
tableView.register(DynamicCell.self, forCellReuseIdentifier: "dynamicCell")
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamicCell", for: indexPath) as! DynamicCell
cell.message = messageArray[indexPath.row]
cell.backgroundColor = .clear
cell.selectionStyle = .none
cell.buildCell()
return cell
}
DynamicCell.swift(代表现在什么都不做):
var backView: UIView!
var label: UILabel!
var picView: UIImageView!
var message: DMessage?
var picViewHeight: NSLayoutConstraint!
var delegate: RefreshCellDelegate?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backView = UIView()
backView.translatesAutoresizingMaskIntoConstraints = false
backView.backgroundColor = .white
backView.clipsToBounds = true
backView.layer.cornerRadius = 8.0
self.addSubview(backView)
label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .left
label.textColor = .black
label.numberOfLines = 0
backView.addSubview(label)
picView = UIImageView()
picView.translatesAutoresizingMaskIntoConstraints = false
picView.clipsToBounds = true
picView.backgroundColor = .red
backView.addSubview(picView)
addMainConstraints()
}
func addMainConstraints() {
backView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8).isActive = true
backView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -32).isActive = true
backView.topAnchor.constraint(equalTo: self.topAnchor, constant: 4).isActive = true
backView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -4).isActive = true
picView.topAnchor.constraint(equalTo: backView.topAnchor, constant: 0).isActive = true
picView.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 0).isActive = true
picView.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: 0).isActive = true
label.topAnchor.constraint(equalTo: picView.bottomAnchor, constant: 0).isActive = true
label.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 8).isActive = true
label.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -8).isActive = true
label.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -4).isActive = true
picViewHeight = NSLayoutConstraint(item: picView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
picViewHeight.priority = UILayoutPriority(999)
picViewHeight.isActive = true
}
override func prepareForReuse() {
picViewHeight.constant = 0
//picViewHeight.constant = 0
}
func buildCell() {
guard let message = message else {return}
label.attributedText = NSAttributedString(string: message.text)
changeHeightWithDelay()
//changeHeightWithoutDelay()
}
func changeHeightWithoutDelay() {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
func changeHeightWithDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
}
将此作为答案。
我注意到一件事,当你在玩 cell 时,最好使用 contentView 而不是直接使用 self。即 self.contentView.addSubview()。 refreshcell 函数有什么作用?您是否尝试过将其标记为 needsSetDisplay,以便在下一个绘制周期中更新它?您试过调用 layoutIfNeeded 吗?
进一步解释一下,您的视图已经 'rendered' 当您想更改视图的 height/width 时,您需要通知它有更新。当您将视图标记为 setNeedsDisplay
并且在下一个渲染周期中它将被更新时会发生这种情况
有关苹果文档的更多信息here
You can use this method or the setNeedsDisplay(_:) to notify the system that your view’s contents need to be redrawn. This method makes a note of the request and returns immediately. The view is not actually redrawn until the next drawing cycle, at which point all invalidated views are updated.