0

I have a UITableView cell with dynamic height and width. Initially, it works properly, but when reusing an old cell the constraints are not set correctly. I am deactivating all the old constrains and activating them again. I have also called setNeedsLayout() and layoutIfNeeded(). But it's not helping.

Automatic height setup: (I think this is causing an issue)

discussionTableView.rowHeight = UITableViewAutomaticDimension
discussionTableView.estimatedRowHeight = 10

My table view cell:

class DiscussionChatMessageCell: UITableViewCell {
    
    private let messageLabel: UILabel
    private let senderNameLabel: UILabel
    private let messageBubble: UIView
    
    let screenWidth: CGFloat
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        messageLabel = UILabel()
        senderNameLabel = UILabel()
        screenWidth = UIScreen.main.bounds.size.width
        messageBubble = UIView()
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        //        self.contentView.backgroundColor = .clear
        
        self.contentView.addSubview(messageBubble)
        messageBubble.translatesAutoresizingMaskIntoConstraints = false
        
        messageBubble.addSubview(senderNameLabel)
        senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
        senderNameLabel.numberOfLines = 0
        senderNameLabel.lineBreakMode = .byCharWrapping
        senderNameLabel.font = UIFont.boldSystemFont(ofSize: 15)
        senderNameLabel.textColor = .white
        
        messageBubble.addSubview(messageLabel)
        messageLabel.translatesAutoresizingMaskIntoConstraints = false
        messageLabel.numberOfLines = 0
        messageLabel.lineBreakMode = .byWordWrapping
        messageLabel.font = UIFont.systemFont(ofSize: 13)
        messageLabel.textColor = .white
        
        NSLayoutConstraint.activate([
            messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
            messageBubble.bottomAnchor.constraint(equalTo:  self.contentView.bottomAnchor, constant: -10),
            messageBubble.widthAnchor.constraint(lessThanOrEqualToConstant: screenWidth - 100),
            
            senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
            senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            
            messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
            messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            messageLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureCell(message: String, isSender: Bool) {
        senderNameLabel.text = "Default Sender"
        messageLabel.text = message
        
        for constraint in messageBubble.constraints {
            //            messageBubble.removeConstraint(constraint)
            constraint.isActive = false
        }
        for constraint in messageLabel.constraints {
            //            messageLabel.removeConstraint(constraint)
            constraint.isActive = false
        }
        for constraint in senderNameLabel.constraints {
            //senderNameLabel.removeConstraint(constraint)
            constraint.isActive = false
        }
        
        NSLayoutConstraint.deactivate([
            messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10),
            messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
        ])
        NSLayoutConstraint.activate([
            messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
            messageBubble.bottomAnchor.constraint(equalTo:  self.contentView.bottomAnchor, constant: -10),
            messageBubble.widthAnchor.constraint(lessThanOrEqualToConstant: screenWidth - 100),
            
            senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
            senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            
            messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
            messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            messageLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
        ])
        
        messageBubble.backgroundColor = isSender ? accentColor : .gray
        if isSender {
            
            NSLayoutConstraint.activate([
                messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10)
            ])
            
            //            let corners: UIRectCorner  = [.topLeft, .topRight, .bottomLeft]
            //            roundCorners(corners: corners, isSender: isSender)
            
        } else {
            NSLayoutConstraint.activate([
                messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
            ])
            
            //            let corners: UIRectCorner  = [.topLeft, .topRight, .bottomRight]
            //            roundCorners(corners: corners, isSender: isSender)
        }
    }

Reusing cell:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let discussionChatMessageCell = tableView.dequeueReusableCell(withIdentifier: discussionChatId, for: indexPath) as? DiscussionChatMessageCell else { return UITableViewCell()}
        
        discussionChatMessageCell.configureCell(message: messages[indexPath.row], isSender: isSender[indexPath.row])

        discussionChatMessageCell.setNeedsLayout()
        discussionChatMessageCell.layoutIfNeeded()
        
        return discussionChatMessageCell
    }

Before reusing cell:

After reusing cell:

Edit

When using UITextView instead of UILabel for messageLabel, the constraints work very differently and the table view takes 2-3 seconds to load.

Changed settings for textView

// messageLabel.numberOfLines = 0
// messageLabel.lineBreakMode = .byWordWrapping
messageLabel.isEditable = false
messageLabel.dataDetectorTypes = .all
messageLabel.textContainer.lineBreakMode = .byWordWrapping
messageLabel.setContentCompressionResistancePriority(.required, for: .vertical)
messageLabel.setContentHuggingPriority(.required, for: .vertical)

Output:

Here's the code for the updated cell, where I have also added a time label. So what is needed is UILable, UITextView, UILabel. And right now this is UILabel, UILabel, UILabel.

class DiscussionChatMessageCell: UITableViewCell {
    
    private let messageLabel: UILabel
    private let senderNameLabel: UILabel
    private let messageSentTimeLabel: UILabel
    private let messageBubble: UIView
    
    private var bubbleLeadingConstraint: NSLayoutConstraint!
    private var bubbleTrailingConstraint: NSLayoutConstraint!
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        
        messageLabel = UILabel()
        senderNameLabel = UILabel()
        messageSentTimeLabel = UILabel()
        messageBubble = UIView()
        
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        self.contentView.addSubview(messageBubble)
        messageBubble.translatesAutoresizingMaskIntoConstraints = false
        
        messageBubble.addSubview(senderNameLabel)
        senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
        senderNameLabel.numberOfLines = 0
        senderNameLabel.lineBreakMode = .byCharWrapping
        senderNameLabel.font = UIFont.boldSystemFont(ofSize: 15)
        senderNameLabel.textColor = .white
        
        messageBubble.addSubview(messageLabel)
        messageLabel.translatesAutoresizingMaskIntoConstraints = false
        
        //        messageLabel.isEditable = false
        //        messageLabel.dataDetectorTypes = .all
        //        messageLabel.textContainer.lineBreakMode = .byWordWrapping
        
        messageLabel.numberOfLines = 0
        messageLabel.lineBreakMode = .byWordWrapping
        messageLabel.font = UIFont(name: "Helvetica Neue", size: 13)!
        
        messageBubble.addSubview(messageSentTimeLabel)
        messageSentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
        messageSentTimeLabel.lineBreakMode = .byCharWrapping
        messageSentTimeLabel.numberOfLines = 0
        messageSentTimeLabel.font = UIFont(name: "HelveticaNeue-Italic", size: 11)!
        
        // set hugging and compression resistance for Name label
        senderNameLabel.setContentCompressionResistancePriority(.required, for: .vertical)
        senderNameLabel.setContentHuggingPriority(.required, for: .vertical)
        
        //        messageLabel.setContentCompressionResistancePriority(.required, for: .vertical)
        //        messageLabel.setContentHuggingPriority(.required, for: .vertical)
        
        // create bubble Leading and Trailing constraints
        bubbleLeadingConstraint = messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
        bubbleTrailingConstraint = messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10)
        
        // priority will be changed in configureCell()
        bubbleLeadingConstraint.priority = .defaultHigh
        bubbleTrailingConstraint.priority = .defaultLow
        
        NSLayoutConstraint.activate([
            
            bubbleLeadingConstraint,
            bubbleTrailingConstraint,
            
            messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
            messageBubble.bottomAnchor.constraint(equalTo:  self.contentView.bottomAnchor, constant: -10),
            
            messageBubble.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor, constant: -100),
            
            senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
            senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            
            messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
            messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            messageLabel.bottomAnchor.constraint(equalTo: messageSentTimeLabel.topAnchor, constant: -10),
            
            messageSentTimeLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            messageSentTimeLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            messageSentTimeLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
            
        ])
        
        // corners will have radius: 10
        messageBubble.layer.cornerRadius = 10
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureCell(message: DiscussionMessage, isSender: Bool) {
        senderNameLabel.text = message.userName + " " + message.userCountryEmoji
        
        let date = Date(timeIntervalSince1970: message.messageTimestamp)
        
        let dayTimePeriodFormatter = DateFormatter()
        dayTimePeriodFormatter.timeZone = .current
        
        dayTimePeriodFormatter.dateFormat = "hh:mm a"
        let dateString = dayTimePeriodFormatter.string(from: date)
        
        messageLabel.text = message.message
        
        messageSentTimeLabel.text = dateString
        
        messageLabel.textColor = isSender ? .black : .white
        senderNameLabel.textColor = isSender ? .black : .white
        messageSentTimeLabel.textColor = isSender ? .black : .white
        messageSentTimeLabel.textAlignment = isSender ? .right : .left
        
        bubbleLeadingConstraint.priority = isSender ? .defaultLow : .defaultHigh
        bubbleTrailingConstraint.priority = isSender ? .defaultHigh : .defaultLow
        
        messageBubble.backgroundColor = isSender ? accentColor : .gray
        
        let senderCorners: CACornerMask = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]
        let nonSenderCorners: CACornerMask =  [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
        
        if #available(iOS 11.0, *) {
            messageBubble.layer.maskedCorners = isSender ?
                // topLeft, topRight, bottomRight
                senderCorners
                :
                // topLeft, topRight, bottomLeft
                nonSenderCorners
        } else {
            // Fallback on earlier versions
            // All corners will be rounded
        }
    }
}

Current output with the time label added to sender name label and message label:

Parth
  • 2,682
  • 1
  • 20
  • 39
  • have you tried `discussionChatMessageCell.messageBubble.layoutIfNeeded()` ? – Mat Jan 13 '21 at 13:32
  • where should I call that? – Parth Jan 13 '21 at 13:32
  • I would try also with messageLabel inside `tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ` something like `discussionChatMessageCell.messageLabel.layoutIfNeeded()` I would try both – Mat Jan 13 '21 at 13:33
  • I think that is what tells the cell how much space it needs – Mat Jan 13 '21 at 13:33
  • that does not seep to help. Also `layoutIfNeeded` will handle the entire subtree layouting – Parth Jan 13 '21 at 13:37
  • Did you try to set contentHugging and contentCompressionResistance on your labels ? label.setContentCompressionResistancePriority(.required, for: .vertical) label.setContentHuggingPriority(.required, for: .vertical) – πter Jan 13 '21 at 13:42
  • which labels should I add this for? – Parth Jan 13 '21 at 13:44
  • that did not help. I added `messageLabel.setContentCompressionResistancePriority(.required, for: .vertical) messageLabel.setContentHuggingPriority(.required, for: .vertical) senderNameLabel.setContentCompressionResistancePriority(.required, for: .vertical) senderNameLabel.setContentHuggingPriority(.required, for: .vertical)` in init – Parth Jan 13 '21 at 14:04

3 Answers3

1

You are modifying constraints way more than you need to.

A better approach would be to create both Leading and Trailing constraints for your "bubble" --- and change their Priority to determine which one is used.

So, if it's a "Received" message, we set the Leading constraint Priority to High, and the Trailing constraint Priority to Low. If it's a "Sent" message, we do the opposite.

Give this a try:

class DiscussionChatMessageCell: UITableViewCell {
    
    let accentColor: UIColor = .systemYellow
    
    private let messageLabel: UILabel
    private let senderNameLabel: UILabel
    private let messageBubble: UIView
    
    private var bubbleLeadingConstraint: NSLayoutConstraint!
    private var bubbleTrailingConstraint: NSLayoutConstraint!

    // not needed
    //let screenWidth: CGFloat

    // wrong signature
    //override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        
        messageLabel = UILabel()
        senderNameLabel = UILabel()
        messageBubble = UIView()

        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        //        self.contentView.backgroundColor = .clear
        
        self.contentView.addSubview(messageBubble)
        messageBubble.translatesAutoresizingMaskIntoConstraints = false
        
        messageBubble.addSubview(senderNameLabel)
        senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
        senderNameLabel.numberOfLines = 0
        senderNameLabel.lineBreakMode = .byCharWrapping
        senderNameLabel.font = UIFont.boldSystemFont(ofSize: 15)
        senderNameLabel.textColor = .white
        
        messageBubble.addSubview(messageLabel)
        messageLabel.translatesAutoresizingMaskIntoConstraints = false
        messageLabel.numberOfLines = 0
        messageLabel.lineBreakMode = .byWordWrapping
        messageLabel.font = UIFont.systemFont(ofSize: 13)
        messageLabel.textColor = .white
        
        // set hugging and compression resistance for Name label
        senderNameLabel.setContentCompressionResistancePriority(.required, for: .vertical)
        senderNameLabel.setContentHuggingPriority(.required, for: .vertical)
        
        // create bubble Leading and Trailing constraints
        bubbleLeadingConstraint = messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
        bubbleTrailingConstraint = messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10)

        // priority will be changed in configureCell()
        bubbleLeadingConstraint.priority = .defaultHigh
        bubbleTrailingConstraint.priority = .defaultLow
        
        NSLayoutConstraint.activate([
            
            bubbleLeadingConstraint,
            bubbleTrailingConstraint,
            
            messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
            messageBubble.bottomAnchor.constraint(equalTo:  self.contentView.bottomAnchor, constant: -10),
            
            messageBubble.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor, constant: -100),
            
            senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
            senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            
            messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
            messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            messageLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
            
        ])
        
        // corners will have radius: 10
        messageBubble.layer.cornerRadius = 10
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureCell(message: String, isSender: Bool) {
        senderNameLabel.text = "Default Sender"
        messageLabel.text = message

        bubbleLeadingConstraint.priority = isSender ? .defaultHigh : .defaultLow
        bubbleTrailingConstraint.priority = isSender ? .defaultLow : .defaultHigh

        messageBubble.backgroundColor = isSender ? accentColor : .gray
        
        messageBubble.layer.maskedCorners = isSender ?
            // topLeft, topRight, bottomRight
            [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
            :
            // topLeft, topRight, bottomLeft
            [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]

    }

}

Side Note: neither of these lines is needed in cellForRowAt:

    //discussionChatMessageCell.setNeedsLayout()
    //discussionChatMessageCell.layoutIfNeeded()

Edit - if you really want to support iOS prior to 11...

I suggest you subclass your "BubbleView" like this:

class BubbleView: UIView {
    var radius: CGFloat = 0
    var corners: UIRectCorner = []
    var color: UIColor = .clear
    
    lazy var shapeLayer: CAShapeLayer = self.layer as! CAShapeLayer
    
    override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        shapeLayer.path = path.cgPath
        shapeLayer.fillColor = color.cgColor
    }
}

and then use it like this:

class DiscussionChatMessageCell: UITableViewCell {
    
    let accentColor: UIColor = .systemYellow
    
    private let messageLabel: UILabel
    private let senderNameLabel: UILabel
    
    // use custom BubbleView class instead of standard UIView
    private let messageBubble: BubbleView
    
    private var bubbleLeadingConstraint: NSLayoutConstraint!
    private var bubbleTrailingConstraint: NSLayoutConstraint!
    
    // wrong signature - I beliee as of Swift 4.2
    // 'UITableViewCellStyle' has been renamed to 'UITableViewCell.CellStyle'
    //override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        
        messageLabel = UILabel()
        senderNameLabel = UILabel()
        messageBubble = BubbleView()
        
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        self.contentView.addSubview(messageBubble)
        messageBubble.translatesAutoresizingMaskIntoConstraints = false
        
        messageBubble.addSubview(senderNameLabel)
        senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
        senderNameLabel.numberOfLines = 0
        senderNameLabel.lineBreakMode = .byCharWrapping
        senderNameLabel.font = UIFont.boldSystemFont(ofSize: 15)
        senderNameLabel.textColor = .white
        
        messageBubble.addSubview(messageLabel)
        messageLabel.translatesAutoresizingMaskIntoConstraints = false
        messageLabel.numberOfLines = 0
        messageLabel.lineBreakMode = .byWordWrapping
        messageLabel.font = UIFont.systemFont(ofSize: 13)
        messageLabel.textColor = .white
        
        // set hugging and compression resistance for Name label
        senderNameLabel.setContentCompressionResistancePriority(.required, for: .vertical)
        senderNameLabel.setContentHuggingPriority(.required, for: .vertical)
        
        // create bubble Leading and Trailing constraints
        bubbleLeadingConstraint = messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
        bubbleTrailingConstraint = messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10)
        
        // priority will be changed in configureCell()
        bubbleLeadingConstraint.priority = .defaultHigh
        bubbleTrailingConstraint.priority = .defaultLow
        
        NSLayoutConstraint.activate([
            
            bubbleLeadingConstraint,
            bubbleTrailingConstraint,
            
            messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
            messageBubble.bottomAnchor.constraint(equalTo:  self.contentView.bottomAnchor, constant: -10),
            
            messageBubble.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor, constant: -100),
            
            senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
            senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            
            messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
            messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
            messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
            messageLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
            
        ])
        
        // corners will have radius: 10
        messageBubble.radius = 10
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureCell(message: String, isSender: Bool) {
        senderNameLabel.text = "Default Sender"
        messageLabel.text = message
        
        bubbleLeadingConstraint.priority = isSender ? .defaultHigh : .defaultLow
        bubbleTrailingConstraint.priority = isSender ? .defaultLow : .defaultHigh
        
        messageBubble.color = isSender ? accentColor : .gray

        let senderCorners: UIRectCorner = [.topLeft, .topRight, .bottomRight]
        let nonSenderCorners: UIRectCorner =  [.topLeft, .topRight, .bottomLeft]

        messageBubble.corners = isSender ? senderCorners : nonSenderCorners

    }
    
}

That will keep the "bubble" view's shape and size, even when the cell changes (such as when rotating the device).

enter image description here

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • OH MY GOD! You are a lifesaver. What was I doing wrong? And is the init you used a newer version? The arguments look the same. Also, based on https://gist.github.com/shaildyp/6cd1de39498ff7b3c22fa758f005f7cd I am modifying the mask part for backward compatibility before ios 11. Let me know is it's correct. – Parth Jan 13 '21 at 14:37
  • @ParthTamane - do you ***really*** need to support iOS versions prior to 11? – DonMag Jan 13 '21 at 14:38
  • Lol! I am currently supporting iOS 9. I think I can take it up to iOS 12, because that is latest version supported by [iPhone 5s](https://iosref.com/ios). What would you recommend? – Parth Jan 13 '21 at 14:43
  • @ParthTamane - if this is an existing app? If so, how many users do you have that are still using iOS 9 or 10? If it's *not* an existing app... no reason to try to support that far back. Of course, I'm only offering an opinion --- it's entirely your decision. – DonMag Jan 13 '21 at 14:54
  • The app is live, but I am not sure how many users are on iOS 9 or 10. But if I were to update the supported version, will these users lose access to previous versions of the app that were supported for their phones? – Parth Jan 13 '21 at 14:56
  • You can view the Analytics / Metrics for your app in AppStoreConnect to get an idea of Platform Version usage. As to current users... suppose you update your app to min iOS 13 ... any user who has kept their device at iOS 10, and already has the app installed, will be able to continue using ***that version*** of the app, but will not be able to ***update*** the app to your latest release (until they update their device's iOS version). – DonMag Jan 13 '21 at 15:05
  • Ya, that makes sense. But will other users on iOS 10 be able to see the app and download it? – Parth Jan 13 '21 at 15:09
  • You can keep earlier versions of the app available, based on "last compatible" or something like that. Search Apple's documentation to find it. By the way - do you have a device (or simulator) at iOS 9 or 10 to test? Your code as written in your suggested edit does not work. If you don't have an old device or sim configuration, change the version check line to `if #available(iOS 20.0, *)` and run it on iOS 13 or 14 to see. – DonMag Jan 13 '21 at 15:25
  • Thanks for the tip! :) I will try that. Also, when you have time, can you tell me how just setting the hugging and compression resistance for one label solved everything? I would think that would be needed for both the labels. – Parth Jan 13 '21 at 15:27
  • The hugging and compression resistance settings aren't *strictly* needed... but without them, if you inspect the views in Debug View Hierarchy, you'll see ambiguity. – DonMag Jan 13 '21 at 15:34
  • Oh! Btw slight issue, I released that I needed to use UITextView instead of UILabel for the messageLabel. But changing it's class completely messes the constraints up. The table view takes 2-3 seconds to load and I can only see the senderNameLabel. Any idea what could be going wrong? – Parth Jan 15 '21 at 04:41
  • I have updated the question with output for UITextView. – Parth Jan 15 '21 at 04:50
  • If you want a `UITextView` to auto-size its height to its text (like a `UILabel`) you have to set `.isScrollEnabled = false`. This has gone far beyond the scope of your original post -- if you have any other issues, you'll need to create a new question. – DonMag Jan 15 '21 at 13:52
  • Thanks, I will do that. – Parth Jan 15 '21 at 15:51
  • Could please you take a look at: [How to vertically overlap and hide labels in a custom UITableView cell with dynamic height and width to reduce cell size and save space](https://stackoverflow.com/questions/66100071/how-to-vertically-overlap-and-hide-labels-in-a-custom-uitableview-cell-with-dyna) when you get some free time? Thanks :) – Parth Feb 08 '21 at 15:36
0

I changed your code setting messageBuble constraint relative to the cell instead of the content view:

 messageBubble.topAnchor.constraint(equalTo: self.topAnchor, constant: 10),
 messageBubble.bottomAnchor.constraint(equalTo:  self.bottomAnchor, constant: -10)

the just calling layoutIfNeeded():

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let discussionChatMessageCell = tableView.dequeueReusableCell(withIdentifier: discussionChatId, for: indexPath) as? DiscussionChatMessageCell else { return UITableViewCell()}
        
discussionChatMessageCell.configureCell(message: messages[indexPath.row], isSender: isSender[indexPath.row])

discussionChatMessageCell.layoutIfNeeded()
        
return discussionChatMessageCell

}
Mat
  • 6,236
  • 9
  • 42
  • 55
-1

You add subviews a lot of times, but it's REUSABLE. Don't forget about it. Add next code before .addSubview(....

contentView.subviews.forEach { $0.removeFromSuperview() }

Or change views values only, don't add it each time