问题
I have a messaging cell that has 3 rows of UILabel, 1. Sender Name, 2. Message 3. Sent Time. Initially, I was keeping an offset of 10 between each of the labels. But I realized that I was wasting space in cases where either the message was too small or if the message wrapped over to a new line and had empty space on the right. So I decided to move the label showing sent time up to save some of that space for the case where the message was too small. I constraint the container like this:
messageBubble.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor, constant: -100)
So I thought I could rely on contentView.frame.width - 100
to get the max container width. But when the reusable cell gets dequeued, this value changes and is not fixed. So for the initial load, my table view cells with small messages look decent but after scrolling for a bit, the width calculations go all wrong. Also, the message label text seems to take up a lot of padding which wastes even more space.
Can someone help me fix the constraints so that the sent time label overlaps the message label if there is space? (Small messages and wrapping to new line messages both). Also, I wanted to hide the sender's name if the previous message in that section was also sent by him. (Eg In the last cell, you will be hidden) I had added the code for it at the end of my cell configuration method. But unfortunately, since this is not a stack view, setting the sender's name UILabel to hidden leaves a blank space. I think this could be solved by setting the height and width of that label to 0 and the priority of its trailing anchor low. And finally setting the top of the message label to the message bubble's top. But since both the bubble width and height are set dynamically I am not sure how to show the sender name label again properly. Since the sender name label's width is also set dynamically and its contents wrap in case of a longer name.
Before scrolling:
Message: Test 1
Sender Name Width: 53.666666666666664
Message Label Width: 47.333333333333336
Sent Time Label Width: 51.666666666666664
Total Message and Sent Time label width: 109.0
Message bubble width: 220.0
Message bubble width from screen: 275.0
Message Bubble Width > Total Sent and Message Width? true
Message: Test message 2 that is a bit longer and goes to line 2
Sender Name Width: 53.666666666666664
Message Label Width: 339.3333333333333
Sent Time Label Width: 51.666666666666664
Total Message and Sent Time label width: 401.0
Message bubble width: 220.0
Message bubble width from screen: 275.0
Message Bubble Width > Total Sent and Message Width? false
After scrolling:
Message: Test 1
Sender Name Width: 53.666666666666664
Message Label Width: 47.333333333333336
Sent Time Label Width: 65536.0
Total Message and Sent Time label width: 65593.33333333333
Message bubble width: 255.0
Message bubble width from screen: 275.0
Message Bubble Width > Total Sent and Message Width? false
Message: Test message 2 that is a bit longer and goes to line 2
Sender Name Width: 65536.0
Message Label Width: 65536.0
Sent Time Label Width: 65536.0
Total Message and Sent Time label width: 131082.0
Message bubble width: 255.0
Message bubble width from screen: 275.0
Message Bubble Width > Total Sent and Message Width? false
Hiererachy with padding of labels:
The custom cell code:
class DiscussionChatMessageCell: UITableViewCell {
private let messageLabel: UITextView
private let senderNameLabel: UILabel
private let messageSentTimeLabel: UILabel
private let messageBubble: UIView
private var bubbleLeadingConstraint: NSLayoutConstraint!
private var bubbleTrailingConstraint: NSLayoutConstraint!
private var messageLabelMessageBubbleTrailingConstraint: NSLayoutConstraint!
private var sentTimeLabelMessageBubbleLeadingConstraint: NSLayoutConstraint!
private var sentTimeLabelMessageLabelLeadingConstraint: NSLayoutConstraint!
private var sentTimeTrailingAnchor: NSLayoutConstraint!
private var positiveOffsetSentTimeTopAnchor: NSLayoutConstraint!
private var negativeOffsetSentTimeTopAnchor: NSLayoutConstraint!
private let verticalSpacing: CGFloat = 0
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
messageLabel = UITextView()
senderNameLabel = UILabel()
messageSentTimeLabel = UILabel()
messageBubble = UIView()
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
self.contentView.addSubview(messageBubble)
messageBubble.translatesAutoresizingMaskIntoConstraints = false
messageBubble.addSubview(senderNameLabel)
senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
senderNameLabel.numberOfLines = 0
senderNameLabel.lineBreakMode = .byCharWrapping
senderNameLabel.font = getFont(name: .HelveticaNeueBold, size: .large)
senderNameLabel.textColor = .white
messageBubble.addSubview(messageLabel)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
messageLabel.delegate = self
messageLabel.isEditable = false
messageLabel.isSelectable = true
messageLabel.dataDetectorTypes = .all
messageLabel.textContainer.lineBreakMode = .byWordWrapping
messageLabel.isScrollEnabled = false
messageLabel.backgroundColor = .clear
// messageLabel.isUserInteractionEnabled = true
messageLabel.font = getFont(name: .HelveticaNeue, size: .medium)
messageBubble.addSubview(messageSentTimeLabel)
messageSentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
messageSentTimeLabel.lineBreakMode = .byCharWrapping
messageSentTimeLabel.numberOfLines = 0
messageSentTimeLabel.font = getFont(name: .HelveticaNeueItalic, size: .small)
// 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: 5)
bubbleTrailingConstraint = messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -5)
messageLabelMessageBubbleTrailingConstraint = messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -5)
sentTimeLabelMessageBubbleLeadingConstraint = messageSentTimeLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 5)
sentTimeLabelMessageLabelLeadingConstraint = messageSentTimeLabel.leadingAnchor.constraint(equalTo: messageLabel.trailingAnchor, constant: 5)
sentTimeTrailingAnchor = messageSentTimeLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -5)
positiveOffsetSentTimeTopAnchor = messageSentTimeLabel.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: verticalSpacing)
negativeOffsetSentTimeTopAnchor = messageSentTimeLabel.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: -1 * 5)
// priority will be changed in configureCell()
bubbleLeadingConstraint.priority = .defaultHigh
bubbleTrailingConstraint.priority = .defaultLow
messageLabelMessageBubbleTrailingConstraint.priority = .defaultHigh
sentTimeLabelMessageBubbleLeadingConstraint.priority = .defaultHigh
sentTimeLabelMessageLabelLeadingConstraint.priority = .defaultLow
positiveOffsetSentTimeTopAnchor.priority = .defaultHigh
negativeOffsetSentTimeTopAnchor.priority = .defaultLow
NSLayoutConstraint.activate([
bubbleLeadingConstraint,
bubbleTrailingConstraint,
messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 5),
messageBubble.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -5),
messageBubble.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor, constant: -100),
senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 5),
senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 5),
// Causes constraint errors that need to be broken
senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -5),
// Causes lesser constraint errors that need to be broken
// senderNameLabel.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor),
messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: verticalSpacing),
messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 5),
messageLabelMessageBubbleTrailingConstraint,
// messageLabel.bottomAnchor.constraint(equalTo: messageSentTimeLabel.topAnchor, constant: -10),
// sentTimeLabelMessageBubbleLeadingConstraint,
sentTimeLabelMessageLabelLeadingConstraint,
// messageSentTimeLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageSentTimeLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -5),
messageSentTimeLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -5),
positiveOffsetSentTimeTopAnchor,
negativeOffsetSentTimeTopAnchor
])
// corners will have radius: 10
messageBubble.layer.cornerRadius = 5
}
func getMessageLabel() -> UITextView {
return messageLabel
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(message: DiscussionMessage, isSender: Bool, previousMessage: DiscussionMessage?) {
let senderName = isSender ? "You" : message.userName
senderNameLabel.text = senderName + " " + 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 ? transparentBlack : transparentWhite
// 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]
let cellWidthFromScreenWidth = UIScreen.main.bounds.width - 100
let senderNameLabelWidth = senderNameLabel.intrinsicContentSize.width
let messageLabelWidth = messageLabel.intrinsicContentSize.width
let sentTimeLabelWidth = messageSentTimeLabel.intrinsicContentSize.width
let messagaeAndSentTimeLabelWidth = messageLabelWidth + sentTimeLabelWidth + 10
let maxMessageBubbleWidth = contentView.frame.width - 100
let shouldShowMessageAndSentTimeInLine: Bool = maxMessageBubbleWidth > messagaeAndSentTimeLabelWidth
print("Message: \(message.message)")
print("Sender Name Width: \(senderNameLabelWidth)")
print("Message Label Width: \(messageLabelWidth)")
print("Sent Time Label Width: \(sentTimeLabelWidth)")
print("Total Message and Sent Time label width: \(messagaeAndSentTimeLabelWidth)")
print("Message bubble width: \(maxMessageBubbleWidth)")
print("Message bubble width from screen: \(cellWidthFromScreenWidth)")
print("Message Bubble Width > Total Sent and Message Width? \(messagaeAndSentTimeLabelWidth < maxMessageBubbleWidth)")
print()
if shouldShowMessageAndSentTimeInLine {
if senderNameLabelWidth < messagaeAndSentTimeLabelWidth {
sentTimeLabelMessageLabelLeadingConstraint.priority = .defaultHigh
messageLabelMessageBubbleTrailingConstraint.priority = .defaultLow
positiveOffsetSentTimeTopAnchor.priority = .defaultLow
negativeOffsetSentTimeTopAnchor.priority = .defaultHigh
} else {
positiveOffsetSentTimeTopAnchor.priority = .defaultLow
negativeOffsetSentTimeTopAnchor.priority = .defaultHigh
}
} else {
sentTimeLabelMessageLabelLeadingConstraint.priority = .defaultLow
messageLabelMessageBubbleTrailingConstraint.priority = .defaultHigh
positiveOffsetSentTimeTopAnchor.priority = .defaultHigh
negativeOffsetSentTimeTopAnchor.priority = .defaultLow
}
messageBubble.layer.maskedCorners = isSender ? senderCorners : nonSenderCorners
// updateLastReadMessageTimestamp(message: message)
//
// if let previousMessage = previousMessage {
// if message.userEmailAddress == previousMessage.userEmailAddress && message.userCountryCode == previousMessage.userCountryCode && isSender {
// senderNameLabel.isHidden = true
// } else {
// senderNameLabel.isHidden = false
// }
// } else {
// senderNameLabel.isHidden = false
// }
}
func updateLastReadMessageTimestamp(message: DiscussionMessage) {
let defaults = UserDefaults.standard
let previousLastReadMessageTimestamp = defaults.double(forKey: lastReadMessageTimestampKey)
if previousLastReadMessageTimestamp < message.messageTimestamp {
defaults.setValue(message.messageTimestamp, forKey: lastReadMessageTimestampKey)
defaults.setValue(message.messageID, forKey: lastReadMessageIDKey)
// print("Saving last read message timestamp: ", message.messageTimestamp, " message id: ", message.messageID)
saveLastReadMessageTimestamp()
}
}
}
extension DiscussionChatMessageCell: UITextViewDelegate {
func textViewDidChangeSelection(_ textView: UITextView) {
textView.selectedTextRange = nil
}
}
来源:https://stackoverflow.com/questions/66100071/how-to-vertically-overlap-and-hide-labels-in-a-custom-uitableview-cell-with-dyna