UITableView 滚动后以编程方式创建的 UITableViewCell 未正确显示
Posted
技术标签:
【中文标题】UITableView 滚动后以编程方式创建的 UITableViewCell 未正确显示【英文标题】:Programmatically created UITableViewCell is not correctly displayed after UITableView scrolling 【发布时间】:2020-07-02 20:31:17 【问题描述】:这是我第一次尝试以编程方式创建的 tableview 单元格。所以也许我正在监督一些基础知识。 我接管并修改了这段代码,以使用 tableview 实现聊天功能。它涵盖了 tableviewcell 的 ChatMessageCell 和 tableview 的 ChatViewController。 ChatMessageCell 检查每条聊天消息,无论是传入(用户与当前用户不同)还是传出。如果是 Incoming,则单元格将固定在白色背景的左侧,如果是 Outgoing,则将其固定在绿色背景的右侧。单元格大小会根据聊天消息的文本进行调整,使其显示在屏幕的左侧或右侧(就像在 WhatsApp 中一样)。
现在的问题: 最初构建 tableview 时,一切都以正确的方式显示,并且单元格被调整到左侧或右侧。一旦用户滚动表格视图,单元格就会被拉伸以占据屏幕的整个宽度,无论它们是传入还是传出。任何提示,有什么问题?
以下是在 ChatViewController 中创建单元格的 sn-p:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! ChatMessageCell
cell.configure(with: messages[indexPath.row])
return cell
还有 ChatMessageCell 类:
picture shows the last cells after the scroll. the first ones are correctly displayed
import Foundation
import UIKit
import FirebaseAuth
class ChatMessageCell: UITableViewCell
let messageLabel = UILabel()
let messageBgView = UIView()
// change background view colour accordingly
var isIncoming: Bool = false
didSet
messageBgView.backgroundColor = isIncoming ? UIColor.white : #colorLiteral(red: 0.8823529412, green: 0.968627451, blue: 0.7921568627, alpha: 1)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(messageBgView)
addSubview(messageLabel)
messageBgView.translatesAutoresizingMaskIntoConstraints = false
messageBgView.layer.cornerRadius = 7
messageLabel.numberOfLines = 0
messageLabel.translatesAutoresizingMaskIntoConstraints = false
// set constraints for the message and the background view
let constraints = [
messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 24),
messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -24),
messageBgView.topAnchor.constraint(equalTo: messageLabel.topAnchor, constant: -16),
messageBgView.leadingAnchor.constraint(equalTo: messageLabel.leadingAnchor, constant: -16),
messageBgView.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 16),
messageBgView.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor, constant: 16)
]
NSLayoutConstraint.activate(constraints)
selectionStyle = .none
backgroundColor = .clear
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
// what we will call from our tableview method
func configure(with model: Chat)
isIncoming = (model.senderId != Auth.auth().currentUser?.uid)
if isIncoming
let sender = model.senderNameInApp
// align to the left
let nameAttributes = [
NSAttributedString.Key.foregroundColor : UIColor.orange,
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)
] as [NSAttributedString.Key : Any]
// sender name at top, message at the next line
let senderName = NSMutableAttributedString(string: sender + "\n", attributes: nameAttributes)
let message = NSMutableAttributedString(string: model.message)
senderName.append(message)
messageLabel.attributedText = senderName
messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32).isActive = true
messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32).isActive = false
else
// align to the right
messageLabel.text = model.message
messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32).isActive = true
messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32).isActive = false
【问题讨论】:
可能不相关,但是当您在运行时修改约束时,强烈建议首先 停用一个约束,然后再激活另一个 【参考方案1】:另一种方法 - 您可能会发现更简单 - 是创建前导和尾随约束 - 一个用于传入,一个用于传出,但给他们不同的Priority
值。要更改对齐方式,您可以交换优先级。
例如,像这样更新您的单元类(数字对应于我添加的 cmets):
-
声明用于对齐的约束变量
设置标签的 ContentHuggingPriority 以防止其填充宽度
创建前导/尾随约束
如果需要,限制消息标签的最大宽度
激活传入和传出约束
根据需要更新约束的优先级
您应该可以直接将其放入以替换您当前的 ChatMessageCell
类:
class ChatMessageCell: UITableViewCell
let messageLabel = UILabel()
let messageBgView = UIView()
// (1) constraints for messsage aligmment
var incomingConstraint: NSLayoutConstraint!
var outgoingConstraint: NSLayoutConstraint!
// change background view colour accordingly
var isIncoming: Bool = false
didSet
messageBgView.backgroundColor = isIncoming ? UIColor.white : #colorLiteral(red: 0.8823529412, green: 0.968627451, blue: 0.7921568627, alpha: 1)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(messageBgView)
addSubview(messageLabel)
messageBgView.translatesAutoresizingMaskIntoConstraints = false
messageBgView.layer.cornerRadius = 7
messageLabel.numberOfLines = 0
messageLabel.translatesAutoresizingMaskIntoConstraints = false
// (2) "hug" the message content
messageLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
// (3) create Leading / Trailing constraints so we can update their Priority later
incomingConstraint = messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32.0)
outgoingConstraint = messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32.0)
// set constraints for the message and the background view
NSLayoutConstraint.activate([
messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 24),
messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -24),
// (4) limit message label to 75% of width (if desired)
messageLabel.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, multiplier: 0.75),
// (5) activate both leading / trailing constraints
incomingConstraint,
outgoingConstraint,
messageBgView.topAnchor.constraint(equalTo: messageLabel.topAnchor, constant: -16),
messageBgView.leadingAnchor.constraint(equalTo: messageLabel.leadingAnchor, constant: -16),
messageBgView.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 16),
messageBgView.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor, constant: 16),
])
selectionStyle = .none
backgroundColor = .clear
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
// what we will call from our tableview method
func configure(with model: Chat)
isIncoming = (model.senderId != Auth.auth().currentUser?.uid)
if isIncoming
let sender = model.senderNameInApp
// align to the left
let nameAttributes = [
NSAttributedString.Key.foregroundColor : UIColor.orange,
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)
] as [NSAttributedString.Key : Any]
// sender name at top, message at the next line
let senderName = NSMutableAttributedString(string: sender + "\n", attributes: nameAttributes)
let message = NSMutableAttributedString(string: model.message)
senderName.append(message)
messageLabel.attributedText = senderName
// (6) update leading / trailing constraint priorities
outgoingConstraint.priority = .defaultLow
incomingConstraint.priority = .defaultHigh
// don't do this here
//messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32).isActive = true
//messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32).isActive = false
else
// align to the right
messageLabel.text = model.message
// (6) update leading / trailing constraint priorities
incomingConstraint.priority = .defaultLow
outgoingConstraint.priority = .defaultHigh
// don't do this here
//messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32).isActive = true
//messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32).isActive = false
【讨论】:
太棒了——效果很好!滚动时,只有在传入消息中使用的发件人姓名的橙色出现在一些传出消息中。我通过覆盖 prepareForReuse 并将 messageLabel.attributedText 和 messageLabel.text 设置为 nil 来解决这个问题。非常感谢!以上是关于UITableView 滚动后以编程方式创建的 UITableViewCell 未正确显示的主要内容,如果未能解决你的问题,请参考以下文章
Apple Watch 应用程序在启动应用程序后以编程方式将视图滚动到顶部
获取 json 数据后以编程方式设置 UITableViewCell 高度
动态地将单元格添加到分组的 uitableview 并以编程方式适应滚动