UITableView 滚动后,以编程方式创建的 UITableViewCell 未正确显示

Programmatically created UITableViewCell is not correctly displayed after UITableView scrolling

这是我第一次尝试以编程方式创建的表格视图单元格。所以也许我正在监督一些基础知识。 我接手并改编了这段代码,以使用 tableview 实现聊天功能。它涵盖了 tableviewcell 的 ChatMessageCell 和 tableview 的 ChatViewController。 ChatMessageCell 检查每条聊天消息,无论是传入消息(用户不同于当前用户)还是传出消息。如果它是传入单元格,则单元格固定在白色背景的左侧,如果它是传出单元格,则固定在绿色背景的右侧。单元格大小会根据聊天消息的文本进行调整,以便它显示在屏幕的左侧或右侧(就像在 WhatsApp 中一样)。

现在问题: 当最初构建 tableview 时,一切都以正确的方式显示,单元格被调整到左侧或右侧。一旦用户滚动表格视图,单元格就会被拉伸以占据屏幕的整个宽度,无论它们是传入还是传出。任何提示,有什么问题吗?

下面是在 ChatViewController 中创建单元格的片段:

    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 class:

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
        }
    }
 
}

另一种方法 - 您可能会发现它更容易 - 是同时创建前导和尾随约束 - 一个用于 incoming 一个用于 outgoing,但给他们不同的Priority值。要更改对齐方式,您可以交换优先级。

例如,像这样更新您的单元格 class(数字对应于我添加的评论):

  1. 声明用于对齐的约束变量
  2. 设置标签的 ContentHuggingPriority 以防止其填满宽度
  3. 创建前导/尾随约束
  4. 如果需要,限制消息标签的最大宽度
  5. 激活传入和传出约束
  6. 根据需要更新约束的优先级

应该能够直接将其放入以替换您当前的 ChatMessageCell class:

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
        }
    }
    
}