UITableView Cell with dynamic height and width not getting reused properly - ios

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:

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).

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
}

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

Related

How do I arrange the Profile Image View to pin next to the Username at the top in the arranged Stack View?

So I have a stack view and the profile image needs to go next to the the username and stay there. How do I do that in this arranged stack view without conflicts because I have tried to anchor it to the top. Like this but no results:
Image of what I am trying to achieve
But currently it keeps doing this:
What is currently happening
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(profileImageView)
contentView.addSubview(profileNameLabel)
contentView.addSubview(userHandel)
profileImageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
let innerPostStackView = UIStackView(arrangedSubviews: [profileNameLabel, userHandel, postTextLabel])
innerPostStackView.axis = .vertical
let postStackView = UIStackView(arrangedSubviews: [profileImageView, innerPostStackView])
postStackView.translatesAutoresizingMaskIntoConstraints = false
postStackView.alignment = .center
postStackView.spacing = 10
contentView.addSubview(postStackView)
NSLayoutConstraint.activate([
postStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
postStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15),
postStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
postTextLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -15)
])
This is what I Have tried with the stack views. I cannot get it to work the way I want it to look.
This how your cell looks like:
class MyCell: UITableViewCell {
let profileNameLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textColor = .black
label.backgroundColor = .clear
label.font = .systemFont(ofSize: 20, weight: .bold)
label.text = "Minions"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let userHandel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textColor = .systemBlue
label.backgroundColor = .clear
label.font = .systemFont(ofSize: 14, weight: .semibold)
label.text = "#Minions"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let postTextLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textColor = .black
label.backgroundColor = .clear
label.text = "Every Mac comes with a one-year limited warranty(opens in a new window) and up to 90 days of complimentary technical support(opens in a new window). AppleCare+ for Mac extends your coverage from your AppleCare+ purchase date and adds unlimited incidents of accidental damage protection, each subject to a service fee of $99 for screen damage or external enclosure damage, or $299 for other accidental damage, plus applicable tax. In addition, you’ll get 24/7 priority access to Apple experts via chat or phone. For complete details, see the terms(opens in a new window)."
return label
}()
let costant: CGFloat = 60
let profileImageView: UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .darkGray.withAlphaComponent(0.2)
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let containerView: UIView = {
let v = UIView()
v.backgroundColor = .clear
return v
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .white
let image = UIImage(named: "minions")?.withRenderingMode(.alwaysOriginal)
profileImageView.image = image
profileImageView.widthAnchor.constraint(equalToConstant: costant).isActive = true // set here profileImageView wudth
profileImageView.layer.cornerRadius = costant / 2
contentView.backgroundColor = .white
containerView.addSubview(profileNameLabel)
profileNameLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
profileNameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
profileNameLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
profileNameLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
containerView.addSubview(userHandel)
userHandel.topAnchor.constraint(equalTo: profileNameLabel.bottomAnchor).isActive = true
userHandel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
userHandel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
userHandel.heightAnchor.constraint(equalToConstant: 20).isActive = true
let totalUpStack = UIStackView(arrangedSubviews: [profileImageView, containerView])
totalUpStack.axis = .horizontal
totalUpStack.spacing = 6
totalUpStack.distribution = .fill
totalUpStack.translatesAutoresizingMaskIntoConstraints = false
totalUpStack.heightAnchor.constraint(equalToConstant: costant).isActive = true
let completeStack = UIStackView(arrangedSubviews: [totalUpStack, postTextLabel])
completeStack.axis = .vertical
completeStack.spacing = 6
completeStack.distribution = .fill
completeStack.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(completeStack)
completeStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true
completeStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
completeStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
completeStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the result:
I'm not sure if it will help you. You can ignore UIStackView and use auto-layout like this:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(profileImageView)
contentView.addSubview(profileNameLabel)
contentView.addSubview(userHandel)
contentView.addSubview(postTextLabel)
// activate autolayout constraints:
NSLayoutConstraint.activate([
// profileImageView:
profileImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
profileImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 16),
profileImageView.heightAnchor.constraint(equalToConstant: 52),
profileImageView.widthAnchor.constraint(equalToConstant: 52),
// profileNameLabel:
profileNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
profileNameLabel.leftAnchor.constraint(equalTo: profileImageView.rightAnchor, constant: 8),
profileNameLabel.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -16),
// userHandel:
userHandel.topAnchor.constraint(equalTo: profileNameLabel.bottomAnchor, constant: 8),
userHandel.leftAnchor.constraint(equalTo: profileNameLabel.leftAnchor),
userHandel.rightAnchor.constraint(equalTo: profileNameLabel.rightAnchor),
// postTextLabel:
postTextLabel.topAnchor.constraint(equalTo: userHandel.bottomAnchor, constant: 8),
postTextLabel.leftAnchor.constraint(equalTo: userHandel.leftAnchor),
postTextLabel.rightAnchor.constraint(equalTo: userHandel.rightAnchor),
postTextLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16)
])
}

Shadow disappears then reappears on UITableView updates

I am using tableView.beginUpdates() and tableView.endUpdates() to expand/contract a cell that has shadow. When the table updates, it removes all shadows from cells and then puts them back as shown here
I have tried using willDisplayCell and also tried changing the shadow to its own view, shadow on contentView and shadow on cell, none worked.
How do i keep the shadow?
extension UIView {
func addTutShadow(shadowOpacity: Float? = nil) {
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 3)
self.layer.shadowRadius = 12 * kHeightFactor
self.layer.shadowOpacity = shadowOpacity ?? 0.12
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TopicListCard
cell.topic = rankedTopics[indexPath.section]
cell.backgroundColor = .clear
cell.backgroundView = UIView()
cell.selectedBackgroundView = UIView()
cell.delegate = self
cell.addTutShadow()
cell.setup()
return cell
}
var selectedIndex = -1
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedIndex != indexPath.section {
selectedIndex = indexPath.section
tableView.beginUpdates()
tableView.endUpdates()
} else {
selectedIndex = -1
tableview.deselectRow(at: indexPath, animated: true)
tableView.beginUpdates()
tableView.endUpdates()
}
}
It's tough to say exactly, without seeing you full cell setup...
I whipped up a quick example of one way to do this...
Looks like this:
Data Structs
struct Topic {
var title: String = ""
var status: String = ""
var icon: String = ""
}
struct TopicCellStruct {
var topic: Topic = Topic()
var expanded: Bool = false
}
Simple gradient view - for the background
class MyGradView: UIView {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
private var gLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
gLayer.colors = [
UIColor(red: 0.71, green: 0.88, blue: 1.0, alpha: 1).cgColor,
UIColor(red: 0.95, green: 0.95, blue: 1.0, alpha: 1).cgColor
]
gLayer.locations = [0, 1]
gLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
}
Example controller class - with some sample generated data
class ShadowTestVC: UIViewController {
var myData: [TopicCellStruct] = []
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
// some sample data
let p1 = "You have correctly answered "
let p2 = "% of the questions you attempted in this topic."
var pct: Int = 5
let titles: [String] = [
"Basic Algebra",
"Isolating a Variable",
"Absolute Value",
"Solving Linear Equations",
"Solving Radical Equations",
]
let syms: [String] = [
"function",
"multiply.circle",
"ruler.fill",
"arrow.up.arrow.down",
"x.squareroot",
]
for (title, sym) in zip(titles, syms) {
pct += 4
let s: String = p1 + "\(pct)" + p2
let t = Topic(title: title, status: s, icon: sym)
let tcs = TopicCellStruct(topic: t, expanded: false)
myData.append(tcs)
}
for i in 6...9 {
pct += 4
let s: String = p1 + "\(pct)" + p2
let t = Topic(title: "Title \(i)", status: s, icon: "\(i).circle.fill")
let tcs = TopicCellStruct(topic: t, expanded: false)
myData.append(tcs)
}
// a label above the table view
let topUI = UILabel()
topUI.font = .systemFont(ofSize: 24, weight: .regular)
topUI.numberOfLines = 0
topUI.text = "This is some text to represent the UI elements above the table view."
// a gradient view for the background
let gradientBKGView = MyGradView()
[gradientBKGView, topUI, tableView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
gradientBKGView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
gradientBKGView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
gradientBKGView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
gradientBKGView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
topUI.topAnchor.constraint(equalTo: g.topAnchor, constant: 32.0),
topUI.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 32.0),
topUI.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -48.0),
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 160.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -16.0),
])
tableView.backgroundColor = .clear
tableView.register(ShadowedCell.self, forCellReuseIdentifier: ShadowedCell.ident)
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
}
}
extension ShadowTestVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: ShadowedCell.ident, for: indexPath) as! ShadowedCell
c.fillData(myData[indexPath.row])
c.selectionStyle = .none
return c
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let c = tableView.cellForRow(at: indexPath) as? ShadowedCell else { return }
myData[indexPath.row].expanded.toggle()
c.isExpanded = myData[indexPath.row].expanded
UIView.animate(withDuration: 0.3, animations: {
tableView.performBatchUpdates(nil, completion: nil)
})
}
}
Example cell class - I made some guesses at your layout, and I set the shadow darker than yours (.shadowOpacity = 0.75) to make it a bit more visible.
class ShadowedCell: UITableViewCell {
public static let ident: String = "sc"
public var isExpanded: Bool = false {
didSet {
expandedConstraint.isActive = isExpanded
ivVerticalExpandedConstraint.isActive = isExpanded
ivHorizontalExpandedConstraint.isActive = isExpanded
}
}
private let titleLabel = UILabel()
private let statusLabel = UILabel()
private let theImageView = UIImageView()
private let getStartedBtn = UIButton()
private let containerView = UIView()
private let shadowView = UIView()
private var collapsedConstraint: NSLayoutConstraint!
private var expandedConstraint: NSLayoutConstraint!
private var ivVerticalCollapsedConstraint: NSLayoutConstraint!
private var ivVerticalExpandedConstraint: NSLayoutConstraint!
private var ivHorizontalCollapsedConstraint: NSLayoutConstraint!
private var ivHorizontalExpandedConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
clipsToBounds = true
contentView.clipsToBounds = true
containerView.clipsToBounds = true
[shadowView, containerView, theImageView, titleLabel, statusLabel, getStartedBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
[titleLabel, statusLabel, getStartedBtn].forEach { v in
v.setContentHuggingPriority(.required, for: .vertical)
v.setContentCompressionResistancePriority(.required, for: .vertical)
}
[titleLabel, statusLabel, theImageView, getStartedBtn].forEach { v in
containerView.addSubview(v)
}
contentView.addSubview(shadowView)
contentView.addSubview(containerView)
contentView.backgroundColor = .clear
self.backgroundColor = .clear
shadowView.backgroundColor = .white
titleLabel.numberOfLines = 0
titleLabel.font = .systemFont(ofSize: 20.0, weight: .regular)
statusLabel.numberOfLines = 0
statusLabel.font = .systemFont(ofSize: 18.0, weight: .regular)
getStartedBtn.setTitle("Get Started", for: [])
getStartedBtn.setTitleColor(.white, for: .normal)
getStartedBtn.setTitleColor(.lightGray, for: .highlighted)
getStartedBtn.backgroundColor = .black
getStartedBtn.layer.cornerRadius = 12
let g = contentView.layoutMarginsGuide
collapsedConstraint = titleLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0.0)
expandedConstraint = getStartedBtn.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16.0)
ivVerticalCollapsedConstraint = theImageView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5.0)
ivVerticalExpandedConstraint = theImageView.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor, constant: 0.0)
ivHorizontalCollapsedConstraint = theImageView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0)
ivHorizontalExpandedConstraint = theImageView.leadingAnchor.constraint(equalTo: statusLabel.trailingAnchor, constant: 16.0)
collapsedConstraint.priority = .required - 2
expandedConstraint.priority = .required - 1
ivVerticalCollapsedConstraint.priority = .required - 2
ivVerticalExpandedConstraint.priority = .required - 1
ivHorizontalCollapsedConstraint.priority = .required - 2
ivHorizontalExpandedConstraint.priority = .required - 1
NSLayoutConstraint.activate([
shadowView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
shadowView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
shadowView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
shadowView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
containerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
titleLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6),
titleLabel.heightAnchor.constraint(equalToConstant: 60.0),
statusLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12.0),
statusLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
statusLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6),
theImageView.widthAnchor.constraint(equalToConstant: 50.0),
theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor),
getStartedBtn.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 32.0),
getStartedBtn.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
getStartedBtn.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0),
collapsedConstraint, ivVerticalCollapsedConstraint, ivHorizontalCollapsedConstraint,
])
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
shadowView.layer.shadowOpacity = 0.75
shadowView.layer.cornerRadius = 16.0
}
public func fillData(_ t: TopicCellStruct) {
titleLabel.text = t.topic.title
statusLabel.text = t.topic.status
if let img = UIImage(systemName: t.topic.icon) {
theImageView.image = img
}
isExpanded = t.expanded
}
}
Give that a try... it should avoid the shadow issue you were seeing. Then compare my approach to yours.
As a side note: I'm sure you already noticed in your development... while the design is very nice with transparent cells and table view, the expand/collapse process looks a bit "quirky" as the cells that were "out of view" (below the bottom of the table view frame) don't really "slide up" along with the other cells.
Edit -- a couple of very minor changes to improve the "glitchy" lower-cells-animation...
All classes*
struct Topic {
var title: String = ""
var status: String = ""
var icon: String = ""
}
struct TopicCellStruct {
var topic: Topic = Topic()
var expanded: Bool = false
}
class ShadowedCell: UITableViewCell {
public static let ident: String = "sc"
private let titleLabel = UILabel()
private let statusLabel = UILabel()
private let theImageView = UIImageView()
private let getStartedBtn = UIButton()
private let containerView = UIView()
private let shadowView = UIView()
private var collapsedConstraint: NSLayoutConstraint!
private var expandedConstraint: NSLayoutConstraint!
private var ivVerticalCollapsedConstraint: NSLayoutConstraint!
private var ivVerticalExpandedConstraint: NSLayoutConstraint!
private var ivHorizontalCollapsedConstraint: NSLayoutConstraint!
private var ivHorizontalExpandedConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
clipsToBounds = true
contentView.clipsToBounds = true
containerView.clipsToBounds = true
[shadowView, containerView, theImageView, titleLabel, statusLabel, getStartedBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
[titleLabel, statusLabel, getStartedBtn].forEach { v in
v.setContentHuggingPriority(.required, for: .vertical)
v.setContentCompressionResistancePriority(.required, for: .vertical)
}
[titleLabel, statusLabel, theImageView, getStartedBtn].forEach { v in
containerView.addSubview(v)
}
contentView.addSubview(shadowView)
contentView.addSubview(containerView)
contentView.backgroundColor = .clear
self.backgroundColor = .clear
shadowView.backgroundColor = .white
titleLabel.numberOfLines = 0
titleLabel.font = .systemFont(ofSize: 20.0, weight: .regular)
statusLabel.numberOfLines = 0
statusLabel.font = .systemFont(ofSize: 18.0, weight: .regular)
getStartedBtn.setTitle("Get Started", for: [])
getStartedBtn.setTitleColor(.white, for: .normal)
getStartedBtn.setTitleColor(.lightGray, for: .highlighted)
getStartedBtn.backgroundColor = .black
getStartedBtn.layer.cornerRadius = 12
let g = contentView.layoutMarginsGuide
collapsedConstraint = titleLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0.0)
expandedConstraint = getStartedBtn.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16.0)
ivVerticalCollapsedConstraint = theImageView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5.0)
ivVerticalExpandedConstraint = theImageView.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor, constant: 0.0)
ivHorizontalCollapsedConstraint = theImageView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0)
ivHorizontalExpandedConstraint = theImageView.leadingAnchor.constraint(equalTo: statusLabel.trailingAnchor, constant: 16.0)
collapsedConstraint.priority = .required - 2
expandedConstraint.priority = .required - 1
ivVerticalCollapsedConstraint.priority = .required - 2
ivVerticalExpandedConstraint.priority = .required - 1
ivHorizontalCollapsedConstraint.priority = .required - 2
ivHorizontalExpandedConstraint.priority = .required - 1
NSLayoutConstraint.activate([
shadowView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
shadowView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
shadowView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
shadowView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
containerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
titleLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6),
titleLabel.heightAnchor.constraint(equalToConstant: 60.0),
statusLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12.0),
statusLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
statusLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6),
theImageView.widthAnchor.constraint(equalToConstant: 50.0),
theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor),
getStartedBtn.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 32.0),
getStartedBtn.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
getStartedBtn.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0),
collapsedConstraint, ivVerticalCollapsedConstraint, ivHorizontalCollapsedConstraint,
])
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
shadowView.layer.shadowOpacity = 0.75
shadowView.layer.cornerRadius = 16.0
}
public func fillData(_ t: TopicCellStruct) {
titleLabel.text = t.topic.title
statusLabel.text = t.topic.status
if let img = UIImage(systemName: t.topic.icon) {
theImageView.image = img
}
expandedConstraint.isActive = t.expanded
ivVerticalExpandedConstraint.isActive = t.expanded
ivHorizontalExpandedConstraint.isActive = t.expanded
}
public func expand(_ isExpanded: Bool) {
expandedConstraint.isActive = isExpanded
ivVerticalExpandedConstraint.isActive = isExpanded
ivHorizontalExpandedConstraint.isActive = isExpanded
UIView.animate(withDuration: 0.3, animations: {
self.layoutIfNeeded()
})
}
}
class ShadowTestVC: UIViewController {
var myData: [TopicCellStruct] = []
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
// some sample data
let p1 = "You have correctly answered "
let p2 = "% of the questions you attempted in this topic."
var pct: Int = 5
let titles: [String] = [
"Basic Algebra",
"Isolating a Variable",
"Absolute Value",
"Solving Linear Equations",
"Solving Radical Equations",
]
let syms: [String] = [
"function",
"multiply.circle",
"ruler.fill",
"arrow.up.arrow.down",
"x.squareroot",
]
for (title, sym) in zip(titles, syms) {
pct += 4
let s: String = p1 + "\(pct)" + p2
let t = Topic(title: title, status: s, icon: sym)
let tcs = TopicCellStruct(topic: t, expanded: false)
myData.append(tcs)
}
for i in 6...19 {
pct += 4
let s: String = p1 + "\(pct)" + p2
let t = Topic(title: "Title \(i)", status: s, icon: "\(i).circle.fill")
let tcs = TopicCellStruct(topic: t, expanded: false)
myData.append(tcs)
}
// a label above the table view
let topUI = UILabel()
topUI.font = .systemFont(ofSize: 24, weight: .regular)
topUI.numberOfLines = 0
topUI.text = "This is some text to represent the UI elements above the table view."
// a gradient view for the background
let gradientBKGView = MyGradView()
[gradientBKGView, topUI, tableView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
gradientBKGView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
gradientBKGView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
gradientBKGView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
gradientBKGView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
topUI.topAnchor.constraint(equalTo: g.topAnchor, constant: 32.0),
topUI.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 32.0),
topUI.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -48.0),
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 160.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -16.0),
])
tableView.backgroundColor = .clear
tableView.register(ShadowedCell.self, forCellReuseIdentifier: ShadowedCell.ident)
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
}
}
extension ShadowTestVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: ShadowedCell.ident, for: indexPath) as! ShadowedCell
c.fillData(myData[indexPath.row])
c.selectionStyle = .none
return c
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let c = tableView.cellForRow(at: indexPath) as? ShadowedCell else { return }
myData[indexPath.row].expanded.toggle()
c.expand(myData[indexPath.row].expanded)
tableView.beginUpdates()
tableView.endUpdates()
}
}
class MyGradView: UIView {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
private var gLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
gLayer.colors = [
UIColor(red: 0.71, green: 0.88, blue: 1.0, alpha: 1).cgColor,
UIColor(red: 0.95, green: 0.95, blue: 1.0, alpha: 1).cgColor
]
gLayer.locations = [0, 1]
gLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
}

Programmatically set constraints for uitableviewcell with uilabel and uiimageview

I am trying to build my tableview with custom UITableViewCell but facing some issues in coinstraints. Following is my custom UITablviewCell class:
class MovieCell: UITableViewCell {
static let identifier = "MovieCell"
let cellMargin = 15.0
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
view.layer.cornerRadius = 5
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
return view
}()
private let movieNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.movieAppBoldFont(size: 15)
label.numberOfLines = 0
return label
}()
private let movieImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
var movieCellViewModel : MoviewCellViewModel = MoviewCellViewModel(image: nil, name: "") {
didSet {
movieNameLabel.text = movieCellViewModel.name
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0)
layoutSubView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func layoutSubView() {
addSubview(containerView)
containerView.addSubview(movieNameLabel)
containerView.addSubview(movieImageView)
let marginGuide = containerView.layoutMarginsGuide
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin),
movieImageView.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 5),
movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 5),
movieImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
movieImageView.widthAnchor.constraint(equalToConstant: 50),
movieImageView.heightAnchor.constraint(equalToConstant: 50),
movieNameLabel.leadingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 5),
movieNameLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: -5),
movieNameLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 5),
movieNameLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -5)
])
}
}
I get the following result with the above code:
I am trying to create a UIView and add content to it. I am adding it because I want to show some empty space as a separator in tableview.
Couple issues...
Your first line in layoutSubView() is:
addSubview(containerView)
where is needs to be:
contentView.addSubview(containerView)
Second, you have movieImageView constrained to contentView:
movieImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
where it should be constrained to marginGuide:
movieImageView.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -5),
Third, you have movieNameLabel.leadingAnchor constrained to marginGuide.trailingAnchor:
movieNameLabel.leadingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 5),
where it should be constrained to movieImageView.trailingAnchor:
movieNameLabel.leadingAnchor.constraint(equalTo: movieImageView.trailingAnchor, constant: 5),
Making those changes will give you this (I set image view background to blue, and label background to cyan):
However, when you run the app, you'll see lots of Unable to simultaneously satisfy constraints. messages. This is common when using subviews with subviews in cells.
To get rid of the auto-layout complaints, we can give the containerView bottom anchor a less-than-required priority:
let bottomC = containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin)
bottomC.priority = .required - 1
and then activate that in your constraints block:
//containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin),
bottomC,
Here's your complete MovieCell class with those changes:
class MovieCell: UITableViewCell {
static let identifier = "MovieCell"
let cellMargin = 15.0
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
view.layer.cornerRadius = 5
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
return view
}()
private let movieNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: 15, weight: .bold) // UIFont.movieAppBoldFont(size: 15)
label.numberOfLines = 0
return label
}()
private let movieImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
var movieCellViewModel : MoviewCellViewModel = MoviewCellViewModel(image: nil, name: "") {
didSet {
movieNameLabel.text = movieCellViewModel.name
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0)
layoutSubView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func layoutSubView() {
contentView.addSubview(containerView)
containerView.addSubview(movieNameLabel)
containerView.addSubview(movieImageView)
let marginGuide = containerView.layoutMarginsGuide
let bottomC = containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin)
bottomC.priority = .required - 1
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
// containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin),
bottomC,
movieImageView.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 5),
movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 5),
// movieImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
movieImageView.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -5),
movieImageView.widthAnchor.constraint(equalToConstant: 50),
movieImageView.heightAnchor.constraint(equalToConstant: 50),
// movieNameLabel.leadingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 5),
movieNameLabel.leadingAnchor.constraint(equalTo: movieImageView.trailingAnchor, constant: 5),
movieNameLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: -5),
movieNameLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 5),
movieNameLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -5)
])
// during dev, so we can easily see the frames
movieImageView.backgroundColor = .systemBlue
movieNameLabel.backgroundColor = .cyan
}
}

UIKit UIImageView with Offset UIImage

I'm currently adding a UIImageView to a Table Cell, and when the cell is populated with data, I'm adding a UIImage (if it exists) to the UIImageView, however, the UIImage is offset like this:
I'm using AutoLayout to setup the Cell, as follows:
import Foundation
import UIKit
class CatalogItemTableCell: UITableViewCell {
let itemImageView: UIImageView = {
let imgView = UIImageView()
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.contentMode = .scaleAspectFit
imgView.clipsToBounds = true
imgView.layer.borderColor = UIColor(named: "FontColor")?.cgColor
imgView.layer.borderWidth = 1.0
imgView.layer.cornerRadius = 4.0
imgView.layer.masksToBounds = true
return imgView
}()
let nameLabel: UILabel = {
let label = UILabel()
label.lineBreakMode = .byTruncatingTail
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFont(ofSize: 18)
return label
}()
let skuLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 10)
return label
}()
let qtyOnHand: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let priceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier);
containerView.addSubview(itemImageView)
containerView.addSubview(nameLabel)
containerView.addSubview(skuLabel)
containerView.addSubview(descriptionLabel)
containerView.addSubview(qtyOnHand)
containerView.addSubview(priceLabel)
self.contentView.addSubview(containerView)
NSLayoutConstraint.activate(
[
containerView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10.0),
containerView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10.0),
containerView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -10.0),
containerView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10.0),
]);
NSLayoutConstraint.activate(
[
itemImageView.topAnchor.constraint(equalTo: containerView.topAnchor),
itemImageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
itemImageView.trailingAnchor.constraint(equalTo: nameLabel.leadingAnchor, constant: -10),
itemImageView.widthAnchor.constraint(equalToConstant: 80),
itemImageView.heightAnchor.constraint(equalToConstant: 80)
]);
NSLayoutConstraint.activate([
nameLabel.topAnchor.constraint(equalTo: containerView.topAnchor),
nameLabel.leadingAnchor.constraint(equalTo: itemImageView.trailingAnchor, constant: 10.0),
nameLabel.bottomAnchor.constraint(equalTo: skuLabel.topAnchor, constant: -10)
]);
NSLayoutConstraint.activate([
skuLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 10.0),
skuLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor),
skuLabel.bottomAnchor.constraint(equalTo: descriptionLabel.topAnchor, constant: -10)
]);
NSLayoutConstraint.activate([
descriptionLabel.topAnchor.constraint(equalTo: skuLabel.bottomAnchor, constant: -10.0),
descriptionLabel.leadingAnchor.constraint(equalTo: skuLabel.leadingAnchor),
descriptionLabel.bottomAnchor.constraint(equalTo: qtyOnHand.topAnchor, constant: -10)
]);
NSLayoutConstraint.activate([
qtyOnHand.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 10.0),
qtyOnHand.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor),
qtyOnHand.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
]);
NSLayoutConstraint.activate([
priceLabel.topAnchor.constraint(equalTo: containerView.topAnchor),
priceLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
]);
}
required init?(coder: NSCoder) {
super.init(coder: coder);
}
}
I'm also setting up the image using Alamoimagefire to scale it correctly, as I couldn't get scaling to work right by just adding the image to the UIImageView
let image = UIImage(contentsOfFile: fileUrl.path)
let size = CGSize(width: 80, height: 80)
let scaledImage = image!.af.imageAspectScaled(toFit: size)
cell.imageView?.image = scaledImage
Any help appreciated to help me understand what would cause this.
First, you shouldn't need to scale the image - using .contentMode = .scaleAspectFit on your image view should work properly.
The problem you are hitting, though, is you're setting the image on the wrong image view...
cell.imageView?.image = scaledImage
should be:
cell.itemImageView.image = scaledImage
but, again, no need to scale it first.

Left or right align shouldShowMenuForRowAt menu for a custom UITableView cell

I have a UITableView that I am using for showing chat messages. The cells of this table view are either left or right-aligned and have dynamic width. The menu displayed by shouldShowMenuForRowAt is shown at the horizontal center of the table view cell. Is there a way to shift it to the left or the right edge?
func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return action == #selector(copy(_:))
}
func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
if action == #selector(copy(_:)) {
if let cell = tableView.cellForRow(at: indexPath) as? DiscussionChatMessageCell {
let pasteboard = UIPasteboard.general
pasteboard.string = cell.getMessageLabel().text
}
}
}
Output:
Can the actions for cells be left and right aligned?
Message Cell:
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!
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.isEditable = false
messageLabel.isSelectable = false
messageLabel.dataDetectorTypes = .all
messageLabel.textContainer.lineBreakMode = .byWordWrapping
messageLabel.isScrollEnabled = false
messageLabel.backgroundColor = .clear
messageLabel.isUserInteractionEnabled = true
// messageLabel.numberOfLines = 0
// messageLabel.lineBreakMode = .byWordWrapping
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: 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
}
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 ? .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]
messageBubble.layer.maskedCorners = isSender ?
// topLeft, topRight, bottomRight
senderCorners
:
// topLeft, topRight, bottomLeft
nonSenderCorners
//
// 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
// }
}
}

Resources