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 并以编程方式适应滚动

如何在设备旋转后以编程方式创建的 UIButtons 保持在屏幕底部?

定义状态变量后以编程方式推送视图

为啥我的 UITableView 单元格在滚动时重叠?