How to make a dynamic change in the height of the ScrollView on which the TableView is added? Swift - swift

Friends, I need help, I spent 2 days on solving this issue. I need to make my ScrollView dynamically change its height based on the amount of text I add to the tableView. The problem is that if I don't strictly set the scroll.contentSize.height height, then the ability to scroll the screen is disabled.
private let scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.contentSize.height = 4000
scroll.backgroundColor = UIColor.white
scroll.translatesAutoresizingMaskIntoConstraints = false
return scroll
}()
func setConstraints() {
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
NSLayoutConstraint.activate([
headLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
headLabel.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 45)
])
NSLayoutConstraint.activate([
ellipseBabyImage.topAnchor.constraint(equalTo: stackViewName.bottomAnchor, constant: 75),
ellipseBabyImage.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
ellipseBabyImage.heightAnchor.constraint(equalToConstant: 131.3),
ellipseBabyImage.widthAnchor.constraint(equalToConstant: 145)
])
NSLayoutConstraint.activate([
goButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
goButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40),
goButton.leadingAnchor.constraint(equalTo: view.leadingAnchor,constant: 20),
goButton.trailingAnchor.constraint(equalTo: view.trailingAnchor,constant: -20),
goButton.heightAnchor.constraint(equalToConstant: 48)
])
NSLayoutConstraint.activate([
characterLabel.topAnchor.constraint(equalTo: ellipseBabyImage.bottomAnchor, constant: 68.35),
characterLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor)
])
NSLayoutConstraint.activate([
stackViewName.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
stackViewName.topAnchor.constraint(equalTo: headLabel.topAnchor,constant: 44),
])
NSLayoutConstraint.activate([
leftBlockStack.topAnchor.constraint(equalTo: fullNameLabel.topAnchor, constant: 60),
leftBlockStack.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 28),
leftBlockStack.rightAnchor.constraint(equalTo: ellipseBabyImage.leftAnchor,constant: -10)
])
NSLayoutConstraint.activate([
rightBlockStack.topAnchor.constraint(equalTo: fullNameLabel.topAnchor, constant: 60),
rightBlockStack.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -28),
rightBlockStack.leftAnchor.constraint(equalTo: ellipseBabyImage.rightAnchor,constant: 10)
])
NSLayoutConstraint.activate([
arrayStackCharacteristicOne.topAnchor.constraint(equalTo: characterLabel.bottomAnchor,constant: 16),
arrayStackCharacteristicOne.centerXAnchor.constraint(equalTo: view.centerXAnchor),
arrayStackCharacteristicOne.leftAnchor.constraint(equalTo: scrollView.leftAnchor,constant: 22),
healthProgressView.heightAnchor.constraint(equalToConstant: 5),
healthProgressView.widthAnchor.constraint(equalToConstant: 90),
willpowerProgressView.heightAnchor.constraint(equalToConstant: 5),
willpowerProgressView.widthAnchor.constraint(equalToConstant: 90),
luckProgressView.heightAnchor.constraint(equalToConstant: 5),
luckProgressView.widthAnchor.constraint(equalToConstant: 90),
])
NSLayoutConstraint.activate([
arrayStackCharacteristicTwo.topAnchor.constraint(equalTo: arrayStackCharacteristicOne.bottomAnchor,constant: 16),
arrayStackCharacteristicTwo.centerXAnchor.constraint(equalTo: view.centerXAnchor),
arrayStackCharacteristicTwo.leftAnchor.constraint(equalTo: scrollView.leftAnchor,constant: 22),
talentProgressView.heightAnchor.constraint(equalToConstant: 5),
talentProgressView.widthAnchor.constraint(equalToConstant: 90),
successProgressView.heightAnchor.constraint(equalToConstant: 5),
successProgressView.widthAnchor.constraint(equalToConstant: 90),
creationProgressView.heightAnchor.constraint(equalToConstant: 5),
creationProgressView.widthAnchor.constraint(equalToConstant: 90),
])
NSLayoutConstraint.activate([
descriptionView.topAnchor.constraint(equalTo: arrayStackCharacteristicTwo.topAnchor, constant: 50),
descriptionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
descriptionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
descriptionView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: 0),
])
class DescriptionView: UIView {
private let characteristicModel = CharacteristicModel()
private let descriptionIdentifier = "CELL"
private let tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .grouped)
tableView.backgroundColor = .none
tableView.bounces = false
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.isScrollEnabled = false
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setDelegates()
setupViews()
setupConstraints()
tableView.sizeToFit()
tableView.register(DescriptionTableViewCell.self, forCellReuseIdentifier: "CELL")
}
required init?(coder: NSCoder) {
fatalError()
}
private func setDelegates() {
tableView.delegate = self
tableView.dataSource = self
}
private func setupViews() {
addSubview(tableView)
translatesAutoresizingMaskIntoConstraints = false
}
}
//MARK: - UI TableViewDataSource
extension DescriptionView: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
characteristicModel.title.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CELL",for: indexPath) as! DescriptionTableViewCell
let descriptionTitle = characteristicModel.title[indexPath.row]
let descriptionText = characteristicModel.description[indexPath.row]
var content = cell.defaultContentConfiguration()
content.textProperties.color = UIColor(red: 0.729, green: 0.443, blue: 0.443, alpha: 1)
content.textProperties.alignment = .center
content.textProperties.font = UIFont(name: "Lato-SemiBold", size: 22) ?? .systemFont(ofSize: 22)
content.text = descriptionTitle
content.secondaryText = descriptionText
content.secondaryTextProperties.font = UIFont(name: "Lato-SemiBold", size: 16) ?? .systemFont(ofSize: 16)
content.secondaryTextProperties.alignment = .justified
cell.contentConfiguration = content
return cell
}
}
extension DescriptionView {
private func setupConstraints() {
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: topAnchor,constant: 5),
tableView.leadingAnchor.constraint(equalTo: leadingAnchor,constant: 5),
tableView.trailingAnchor.constraint(equalTo: trailingAnchor,constant: -5),
tableView.bottomAnchor.constraint(equalTo: bottomAnchor,constant: -5)
])
}
}
I tried to use different approaches that I found on the Internet, but unfortunately I could not solve it. Either scroll is disabled for me, or the scroll width does not change automatically.
The screenshot shows that I use a tableView for the text that will be automatically added from the API, everything else is uiScrollView. tableView should have scroll disabled and scrollView should scroll to the end of the text
The application should look like this.

Set your scrollview and tableview height to 0 and try adding this to your viewcontroller. you can update the height of the scrollview through viewDidLayoutSubviews.
override func viewDidLayoutSubviews() {
DispatchQueue.main.async {
self.scrollView.contentSize.height = self.descriptionView.tableView.contentSize.height + otherViews.frame.height
}
}
you can also add an observer to the tableview whenever it updates its contentsize
override func viewDidLoad() {
descriptionView.tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let obj = object as? UITableView {
if obj == descriptionView.tableView && keyPath == "contentSize" {
if let newSize = change?[.newKey] as? CGSize {
descriptionView.tableViewHeightConstraint.constant = newSize.height
view.layoutIfNeeded()
}
}
}
}

Related

Programmatic NSScrollView with layout anchors - how to fix the scrolling?

Quite a few SO posts on implementing a scroll view programmatically but I haven't found a solution for this case... here I am adding subviews to the document view (with everything laid out using layout anchors), which all works apart from the scrolling.
I think the issue here is that AppKit interprets the constraints in the example to mean there isn't anything to scroll, but I am not sure why...
import Cocoa
class ViewController: NSViewController {
var scrollView = NSScrollView()
var contentView = NSClipView()
var documentView = ParentView()
var generateButton = NSButton()
#objc func generate(_ sender: NSObject) {
let child = ChildView()
child.translatesAutoresizingMaskIntoConstraints = false
documentView.addSubview(child)
documentView.setupChildViewLayout(sv: child)
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(generateButton)
view.addSubview(scrollView)
setupLayout()
}
func setupLayout() {
scrollView.contentView = contentView
scrollView.documentView = documentView
scrollView.borderType = .lineBorder
scrollView.hasHorizontalScroller = true
scrollView.hasVerticalScroller = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
documentView.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints([
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
scrollView.trailingAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -150),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80),
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100)
])
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
scrollView.trailingAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -150),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80),
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100)
])
scrollView.addConstraints([
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentView.trailingAnchor.constraint(greaterThanOrEqualTo: scrollView.trailingAnchor),
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.bottomAnchor)
])
NSLayoutConstraint.activate([
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentView.trailingAnchor.constraint(greaterThanOrEqualTo: scrollView.trailingAnchor),
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.bottomAnchor)
])
scrollView.addConstraints([
documentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
documentView.trailingAnchor.constraint(greaterThanOrEqualTo: scrollView.trailingAnchor),
documentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
documentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.bottomAnchor)
])
NSLayoutConstraint.activate([
documentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
documentView.trailingAnchor.constraint(greaterThanOrEqualTo: scrollView.trailingAnchor),
documentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
documentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.bottomAnchor)
])
// Setup anchor constraints for the button
setupGenerateButton()
generateButton.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints([
generateButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
generateButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
generateButton.heightAnchor.constraint(equalToConstant: 30),
generateButton.widthAnchor.constraint(equalToConstant: 200)
])
NSLayoutConstraint.activate([
generateButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
generateButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
generateButton.heightAnchor.constraint(equalToConstant: 30),
generateButton.widthAnchor.constraint(equalToConstant: 200)
])
}
func setupGenerateButton() {
generateButton.attributedTitle = NSMutableAttributedString(string: "GenerateChildView", attributes: [NSAttributedString.Key.strokeColor: (NSColor.white), NSAttributedString.Key.font: NSFont.systemFont(ofSize: (NSFont.systemFontSize))])
generateButton.wantsLayer = true
generateButton.bezelColor = NSColor(red: 0.2, green: 0.2, blue: 0.6, alpha: 1.0)
generateButton.action = #selector(self.generate(_:))
}
}
class ParentView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
func setupChildViewLayout(sv: ChildView) {
if (self.subviews.count < 2) {
self.addConstraints([
sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
sv.topAnchor.constraint(equalTo: self.topAnchor)
])
NSLayoutConstraint.activate([
sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
sv.topAnchor.constraint(equalTo: self.topAnchor)
])
}
else {
let c = self.subviews.count - 2
let lastView = self.subviews[c]
self.addConstraints([
sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
sv.topAnchor.constraint(equalTo: lastView.bottomAnchor)
])
NSLayoutConstraint.activate([
sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
sv.topAnchor.constraint(equalTo: lastView.bottomAnchor)
])
}
}
}
class ChildView: NSView {
override var intrinsicContentSize: NSSize {
return CGSize(width: 650, height: 200)
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NSColor.gray.set()
self.bounds.frame()
}
}
You're still pinning the content view and the document view to the scroll view. Only add constraints to the outside of the scroll view and the inside of the document view. Don't add constraints between the content view, the document view and the scroll view. Add constraints between the parent view and its child views so the childs views will fit inside the parent view.
class ViewController: NSViewController {
var scrollView = NSScrollView()
var documentView = ParentView()
var generateButton = NSButton()
#objc func generate(_ sender: NSObject) {
let child = ChildView()
child.translatesAutoresizingMaskIntoConstraints = false
documentView.addSubview(child)
documentView.setupChildViewLayout(sv: child)
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(generateButton)
view.addSubview(scrollView)
setupLayout()
}
func setupLayout() {
scrollView.documentView = documentView
scrollView.borderType = .lineBorder
scrollView.hasHorizontalScroller = true
scrollView.hasVerticalScroller = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
documentView.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints([
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -150),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80),
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100)
])
// Setup anchor constraints for the button
setupGenerateButton()
generateButton.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints([
generateButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
generateButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
generateButton.heightAnchor.constraint(equalToConstant: 30),
generateButton.widthAnchor.constraint(equalToConstant: 200)
])
}
func setupGenerateButton() {
generateButton.attributedTitle = NSMutableAttributedString(string: "GenerateChildView", attributes: [NSAttributedString.Key.strokeColor: (NSColor.white), NSAttributedString.Key.font: NSFont.systemFont(ofSize: (NSFont.systemFontSize))])
generateButton.wantsLayer = true
generateButton.bezelColor = NSColor(red: 0.2, green: 0.2, blue: 0.6, alpha: 1.0)
generateButton.action = #selector(self.generate(_:))
}
}
class ParentView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
func setupChildViewLayout(sv: ChildView) {
if (self.subviews.count == 1) {
self.addConstraints([
sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
sv.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor),
sv.topAnchor.constraint(equalTo: self.topAnchor),
sv.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
else {
let c = self.subviews.count - 2
let lastView = self.subviews[c]
self.addConstraints([
sv.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
sv.topAnchor.constraint(equalTo: lastView.bottomAnchor)
])
let bottomConstraints = self.constraints.filter {
$0.firstAttribute == NSLayoutConstraint.Attribute.bottom
}
self.removeConstraints(bottomConstraints)
self.addConstraints([
sv.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
}
}
class ChildView: NSView {
override var intrinsicContentSize: NSSize {
return CGSize(width: 650, height: 200)
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NSColor.gray.set()
self.bounds.frame()
}
}

Expand UITableViewCell (custom) on tap - autosize cell - some issues

In my project (UIKit, programmatic UI) I have a UITableView with sections. The cells use a custom class. On load all cells just show 3 lines of info (2 labels). On tap, all contents will be displayed. Therefor I've setup my custom cell class to have two containers, one for the 3 line preview and one for the full contents. These containers are added/removed from the cell's content view when needed when the user taps the cell by calling a method (toggleFullView) on the custom cell class. This method is called from the view controller in didSelectRowAt:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let annotation = annotationsController.getAnnotationFor(indexPath)
//Expandable cell
guard let cell = tableView.cellForRow(at: indexPath) as? AnnotationCell else { return }
cell.toggleFullView()
tableView.reloadRows(at: [indexPath], with: .none)
// tableView.reloadData()
}
Basically it works, but there are some issues:
I have to double tap the cell for it to expand and again to make it collapse again. The first tap will perform the row animation of tableView.reloadRows(at: [indexPath], with: .none) and the second tap will perform the expanding. If I substitute reloadRows with tableView.reloadData() the expanding and collapsing will happen after a single tap! But that is disabling any animations obviously, it just snaps into place. How Do I get it to work with one tap?
When the cell expands, some other random cells are also expanded. I guess this has something to do with reusable cells, but I have not been able to remedy this. See the attached Video (https://www.youtube.com/watch?v=rOkuqMnArEU).
I want to be the expanded cell to collapse once I tap another cell to expand, how do I perceive that?
My custom cell class:
import UIKit
class AnnotationCell: UITableViewCell, SelfConfiguringAnnotationCell {
//MARK: - Properties
private let titleLabelPreview = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let titleLabelDetails = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let detailsLabelShort = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 2)
private let detailsLabelLong = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 0)
private let mapImageLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let lastEditedLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let checkmarkImageView = UIImageView()
private var checkmarkView = UIView()
private var previewDetailsView = UIStackView()
private var fullDetailsView = UIStackView()
private var showFullDetails = false
//MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureContents()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
let padding: CGFloat = 5
if contentView.subviews.contains(previewDetailsView) {
//Constrain the preview view
NSLayoutConstraint.activate([
previewDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
previewDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
previewDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
previewDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding)
])
} else {
//Constrain the full view
NSLayoutConstraint.activate([
fullDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
fullDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
fullDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
fullDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding)
])
}
}
//MARK: - Actions
///Expand and collapse the cell
func toggleFullView() {
showFullDetails.toggle()
if showFullDetails {
//show the full version
if contentView.subviews.contains(previewDetailsView) {
previewDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(fullDetailsView) {
contentView.addSubview(fullDetailsView)
}
} else {
//show the preview version
if contentView.subviews.contains(fullDetailsView) {
fullDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(previewDetailsView) {
contentView.addSubview(previewDetailsView)
}
}
UIView.animate(withDuration: 1.2) {
self.layoutIfNeeded()
}
}
//MARK: - Layout
private func configureContents() {
backgroundColor = .clear
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
selectionStyle = .none
detailsLabelShort.adjustsFontSizeToFitWidth = false
detailsLabelLong.adjustsFontSizeToFitWidth = false
checkmarkView.translatesAutoresizingMaskIntoConstraints = false
checkmarkView.addSubview(checkmarkImageView)
checkmarkImageView.tintColor = .systemOrange
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView = UIStackView(arrangedSubviews: [titleLabelPreview, detailsLabelShort])
previewDetailsView.axis = .vertical
previewDetailsView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView.addBackground(.blue)
fullDetailsView = UIStackView(arrangedSubviews: [titleLabelDetails, detailsLabelLong, mapImageLabel, lastEditedLabel])
fullDetailsView.axis = .vertical
fullDetailsView.translatesAutoresizingMaskIntoConstraints = false
fullDetailsView.addBackground(.green)
//By default only add the preview View
contentView.addSubviews(checkmarkView, previewDetailsView)
let padding: CGFloat = 5
NSLayoutConstraint.activate([
//Constrain the checkmark image view to the top left with a fixed height and width
checkmarkImageView.widthAnchor.constraint(equalToConstant: 24),
checkmarkImageView.heightAnchor.constraint(equalTo: checkmarkImageView.widthAnchor),
checkmarkImageView.centerYAnchor.constraint(equalTo: checkmarkView.centerYAnchor),
checkmarkImageView.centerXAnchor.constraint(equalTo: checkmarkView.centerXAnchor),
checkmarkView.widthAnchor.constraint(equalToConstant: 30),
checkmarkView.heightAnchor.constraint(equalTo: checkmarkView.widthAnchor),
checkmarkView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
checkmarkView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: padding)
])
self.layoutIfNeeded()
}
//MARK: - Configure cell with data
func configure(with annotation: AnnotationsController.Annotation) {
titleLabelPreview.text = annotation.title
titleLabelDetails.text = annotation.title
detailsLabelShort.text = annotation.details
detailsLabelLong.text = annotation.details
checkmarkImageView.image = annotation.complete ? ProjectImages.Annotation.checkmark : nil
lastEditedLabel.text = annotation.lastEdited.customMediumToString
mapImageLabel.text = annotation.mapImage?.title ?? "No map image attached"
}
}
Ok, got it fixed, a fully expanding tableview cell. Key things are invalidating the layout in the custom cell class and calling beginUpdates() and endUpdates() on the tableView!
In my viewController:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Expandable cell
guard let cell = tableView.cellForRow(at: indexPath) as? AnnotationCell else { return }
cell.toggleFullView()
tableView.beginUpdates()
tableView.endUpdates()
}
and my custom cell class with the toggleFullView() method:
class AnnotationCell: UITableViewCell, SelfConfiguringAnnotationCell {
//MARK: - Properties
private let titleLabelPreview = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let titleLabelDetails = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .headline))
private let detailsLabelShort = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 2)
private let detailsLabelLong = ProjectTitleLabel(withTextAlignment: .left, andFont: UIFont.preferredFont(forTextStyle: .subheadline), numberOfLines: 0)
private let mapImageLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let lastEditedLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemGroupedBackground)
private let checkmarkImageView = UIImageView()
private var checkmarkView = UIView()
private var previewDetailsView = UIStackView()
private var fullDetailsView = UIStackView()
let padding: CGFloat = 5
var showFullDetails = false
//MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureContents()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Actions
///Expand and collapse the cell
func toggleFullView() {
//Show the full contents
print("ShowFullDetails = \(showFullDetails.description.uppercased())")
if showFullDetails {
print("Show full contents")
if contentView.subviews.contains(previewDetailsView) {
previewDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(fullDetailsView) {
contentView.addSubview(fullDetailsView)
}
NSLayoutConstraint.activate([
fullDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
fullDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
fullDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
fullDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding)
])
//Show preview contents
} else {
print("Show preview contents")
if contentView.subviews.contains(fullDetailsView) {
fullDetailsView.removeFromSuperview()
}
if !contentView.subviews.contains(previewDetailsView) {
contentView.addSubview(previewDetailsView)
}
NSLayoutConstraint.activate([
previewDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
previewDetailsView.leadingAnchor.constraint(equalTo: checkmarkView.trailingAnchor, constant: padding),
previewDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2 * padding),
previewDetailsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
showFullDetails.toggle()
//Invalidate current layout &
self.setNeedsLayout()
}
override func prepareForReuse() {
//Make sure reused cells start in the preview mode!
// showFullDetails = false
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
NSLayoutConstraint.activate([
//Constrain the checkmark image view to the top left with a fixed height and width
checkmarkImageView.widthAnchor.constraint(equalToConstant: 24),
checkmarkImageView.heightAnchor.constraint(equalTo: checkmarkImageView.widthAnchor),
checkmarkImageView.centerYAnchor.constraint(equalTo: checkmarkView.centerYAnchor),
checkmarkImageView.centerXAnchor.constraint(equalTo: checkmarkView.centerXAnchor),
checkmarkView.widthAnchor.constraint(equalToConstant: 30),
checkmarkView.heightAnchor.constraint(equalTo: checkmarkView.widthAnchor),
checkmarkView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding),
checkmarkView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: padding)
])
}
//MARK: - Layout
private func configureContents() {
//Setup Views
backgroundColor = .clear
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
selectionStyle = .none
detailsLabelShort.adjustsFontSizeToFitWidth = false
detailsLabelLong.adjustsFontSizeToFitWidth = false
checkmarkView.translatesAutoresizingMaskIntoConstraints = false
checkmarkView.addSubview(checkmarkImageView)
checkmarkImageView.tintColor = .systemOrange
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView = UIStackView(arrangedSubviews: [titleLabelPreview, detailsLabelShort])
previewDetailsView.axis = .vertical
previewDetailsView.translatesAutoresizingMaskIntoConstraints = false
previewDetailsView.addBackground(.blue)
fullDetailsView = UIStackView(arrangedSubviews: [titleLabelDetails, detailsLabelLong, mapImageLabel, lastEditedLabel])
fullDetailsView.axis = .vertical
fullDetailsView.translatesAutoresizingMaskIntoConstraints = false
fullDetailsView.addBackground(.green)
//By default only show the preview View
contentView.addSubviews(checkmarkView)
//Setup preview/DetailView
toggleFullView()
}
//MARK: - Configure cell with data
func configure(with annotation: AnnotationsController.Annotation) {
titleLabelPreview.text = annotation.title
titleLabelDetails.text = annotation.title
detailsLabelShort.text = annotation.details
detailsLabelLong.text = annotation.details
checkmarkImageView.image = annotation.complete ? ProjectImages.Annotation.checkmark : nil
lastEditedLabel.text = annotation.lastEdited.customMediumToString
mapImageLabel.text = annotation.mapImage?.title ?? "No map image attached"
}
}
HTH!

Separator line appear early than cell title and image goes beyond of UIImageView

I programmatically created UITableView and Cell. All work good, but I have a couple questions about constraints and auto layout:
Constraints with layoutMarginsGuide:
In screens where width 375, lower separator and cell starts from one point. On big size screens, separator appears earlier than the cell title(you can see this in attached screenshot). How I can fix this?
The image of the red rectangle with round corners in the attached screenshot goes beyond of UIImageView. I added constraints to UIImageView, but this not help me. How I can fix this?
Constraints without layoutMarginsGuide:
I want that stackView to cover the whole cell. How can I add constraints without using layoutMarginsGuide and avoid console warnings?
How can I add height constraints? I want that my stackView or title was 100 pt.
Screenshot, cell icon, and code you can found below.
Thanks for your answer.
Rectangle with rounded corners
Screenshot:
Code:
import Foundation
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// MARK: - Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
let tableViewstyle = UITableView.Style.insetGrouped
let tableView = UITableView(frame: CGRect(), style: tableViewstyle)
tableView.delegate = self
tableView.dataSource = self
tableView.register(Cell1.self, forCellReuseIdentifier: String(Cell1.description()))
tableView.tableFooterView = UIView()
tableView.translatesAutoresizingMaskIntoConstraints = false
self.view.backgroundColor = .systemBackground
self.view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.widthAnchor.constraint(equalTo: self.view.widthAnchor),
tableView.heightAnchor.constraint(equalTo: self.view.heightAnchor)
])
}
// MARK: - numberOfSections
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// MARK: - numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 7
}
// MARK: - cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(Cell1.description())) as! Cell1
cell.title.text = "Cell \(indexPath.row)"
cell.icon2.image = UIImage(named: "Icon")
return cell
}
}
// MARK: - Cell
class Cell1: UITableViewCell {
// MARK: - Properties
let title = UILabel()
let icon2 = UIImageView()
let stackView = UIStackView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Off translatesAutoresizingMaskIntoConstraints
title.translatesAutoresizingMaskIntoConstraints = false
icon2.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
// Set constraints
icon2.addConstraint(NSLayoutConstraint(item: icon2, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30))
icon2.addConstraint(NSLayoutConstraint(item: icon2, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30))
// StackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// TitleAndDetailStackView
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
// Hugging
title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
// Resistance
title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
icon2.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
title.textAlignment = .left
title.numberOfLines = 0
// Highlight stackView and set colors
stackView.addBackground(color: .green)
title.textColor = .label
icon2.backgroundColor = .cyan
// Add title and detail
stackView.addArrangedSubview(title)
stackView.addArrangedSubview(icon2)
icon2.contentMode = .center
// Add to subview
self.contentView.addSubview(stackView)
// Get layoutMarginsGuide
let layoutMarginsGuide = contentView.layoutMarginsGuide
// Set constraints
NSLayoutConstraint.activate([
// Constrain all 4 sides of the stack view to the content view's layoutMarginsGuide
stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 0.0),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
// MARK: - AddBackground
extension UIStackView {
func addBackground(color: UIColor) {
let subView = UIView(frame: bounds)
subView.backgroundColor = color
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
insertSubview(subView, at: 0)
}
}
To achieve this we need to change constraints to contentView. and set leadingAnchor.constraint constant equal to 16.0.
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
])
Change icon2.contentMode = .center to icon2.contentMode = .scaleAspectFit
StackView that cover the whole cell:
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
])
To change cell height we can simply add height constraint to any object inside the cell:
title.addConstraint(NSLayoutConstraint(item: title, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100))

Change view with Segment View Control

I have created a view in my View controller which has UISegmentControl and a UIScrollView
let segmentControl : UISegmentedControl = {
let segmentItems = ["Personal","Statistics","Calendar"]
let segmentControl = UISegmentedControl(items: segmentItems)
segmentControl.selectedSegmentIndex = 0
segmentControl.addTarget(self, action: #selector(selectIn(sender:)), for: .valueChanged)
segmentControl.translatesAutoresizingMaskIntoConstraints = false
return segmentControl
}()
let subView : UIScrollView = {
let view = UIScrollView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
In viewDidLoad I added
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.isHidden = true
view.addSubview(segmentControl)
view.addSubview(subView)
setLayout()
}
and added layout as follows
func setLayout(){
NSLayoutConstraint.activate([
segmentControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
segmentControl.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
segmentControl.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
segmentControl.heightAnchor.constraint(equalToConstant: 30),
subView.topAnchor.constraint(equalTo: segmentControl.bottomAnchor, constant: 10),
subView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
subView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
subView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
and tried to add PersonalView (UIView) when any segmentControl is pressed
#objc func selectIn(sender: UISegmentedControl){
subView.addSubview(pvc)
}
My personalView is as follows
class PersonalView: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UITableViewDelegate, UITableViewDataSource {
let tempValueForTable : Int = 10
let todayLabel : UILabel = {
let label = UILabel()
label.text = "Today"
label.font = .montserratSemiBold
label.textAlignment = .center
label.backgroundColor = .green
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(todayLabel)
setLayout()
}
private func setLayout(){
NSLayoutConstraint.activate([
todayLabel.topAnchor.constraint(equalTo: self.topAnchor),
todayLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
todayLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20),
todayLabel.heightAnchor.constraint(equalToConstant: 25),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}}
Still I am not able to get view and in further update I need to add more view in same ScrollView
1.when you are using auto layout constraints you must set this false after add.
SomeView.translatesAutoresizingMaskIntoConstraints = false
2.After you add your subView you must set its constraints and call view.layoutIfNeeded()
subView.addSubview(pvc)
pvc.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
pvc.trailingAnchor.constraint(equalTo: subView.trailingAnchor),
pvc.leadingAnchor.constraint(equalTo: subView.leadingAnchor),
pvc.topAnchor.constraint(equalTo: subView.topAnchor),
pvc.bottomAnchor.constraint(equalTo: subView.bottomAnchor),
])
view.layoutIfNeeded()

The last cell in UITableView always has the same height as the tallest cell in the table?

I have a UITableView that displays comments. The last cell always has the same height as the tallest cell in the UITableView and I do not know why. With reference to the image below, it can be seen that the height of the last custom object (LabelWithPadding) that holds the comment is the same height as the tallest custom object in the same table. Please can someone advise?
class CommentOnPostTableView: UITableView, UITableViewDataSource, UITableViewDelegate{
var items: [Comment]?
var post: ResearchPost?
let cellIDB = "commentCellId"
init(frame: CGRect, style: UITableView.Style, sourceData: [Comment], postContent: ResearchPost) {
super.init(frame: frame, style: style)
items = sourceData
post = postContent
self.dataSource = self
self.delegate = self
self.register(CommentTableViewCell.self, forCellReuseIdentifier: cellIDB)
//self.separatorStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/**Delegate functions*/
extension CommentOnPostTableView{
/***Number of cells*/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let dataSource = items{
return dataSource.count
}else{
return 0
}
}
/***Create Cell*/
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let dataSource = items else{return UITableViewCell.init()}
let cell = tableView.dequeueReusableCell(withIdentifier: cellIDB, for: indexPath) as! CommentTableViewCell
cell.dataObject = dataSource[indexPath.row]
return cell
}
class CommentTableViewCell: UITableViewCell {
var commentLabel: LabelWithPadding!
var imgView: UIImageView = {
let view = UIImageView()
view.backgroundColor = UIColor.black
view.contentMode = .scaleAspectFit
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var nameLabel: UILabel = {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 11)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var dataObject: Comment?{
didSet{
if let data = dataObject{
guard
let image = data.imageOfCommentor,
let name = data.name,
let comment = data.comment else{return}
imgView.image = image
nameLabel.text = name
let padding = UIEdgeInsets.init(top: 3, left: 5, bottom: 3, right: 5)
commentLabel = LabelWithPadding.init(frame: .zero, with: padding)
commentLabel.translatesAutoresizingMaskIntoConstraints = false
commentLabel.label.text = comment
setupViews()
}
}
}
....
// Auto layout constraints
private func setupViews(){
let c = contentView.safeAreaLayoutGuide
contentView.addSubview(imgView)
contentView.addSubview(nameLabel)
contentView.addSubview(commentLabel)
imgView.topAnchor.constraint(equalTo: c.topAnchor, constant: borderSpace).isActive = true
imgView.leadingAnchor.constraint(equalTo: c.leadingAnchor, constant: borderSpace).isActive = true
imgView.widthAnchor.constraint(equalTo: c.widthAnchor, multiplier: 0.08).isActive = true
imgView.heightAnchor.constraint(equalTo: c.widthAnchor, multiplier: 0.08).isActive = true
nameLabel.topAnchor.constraint(equalTo: c.topAnchor, constant: borderSpace).isActive = true
nameLabel.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 10).isActive = true
nameLabel.trailingAnchor.constraint(equalTo: c.trailingAnchor, constant: -borderSpace).isActive = true
commentLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: viewSpace).isActive = true
commentLabel.widthAnchor.constraint(equalTo: nameLabel.widthAnchor).isActive = true
commentLabel.trailingAnchor.constraint(equalTo: c.trailingAnchor, constant: -borderSpace).isActive = true
commentLabel.bottomAnchor.constraint(equalTo: c.bottomAnchor).isActive = true
commentLabel.label.font = UIFont.systemFont(ofSize: 12)
commentLabel.label.numberOfLines = 0
commentLabel.label.lineBreakMode = .byWordWrapping
commentLabel.layer.cornerRadius = 7
commentLabel.layer.masksToBounds = true
commentLabel.backgroundColor = UIColor.appGrayExtraLightGray
}
}
Custom Class:
class LabelWithPadding: UIView {
var padding: UIEdgeInsets!
let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
init(frame: CGRect, with padding: UIEdgeInsets) {
super.init(frame: frame)
self.padding = padding
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView(){
self.addSubview(label)
label.topAnchor.constraint(equalTo: self.topAnchor, constant: padding.top).isActive = true
label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -padding.bottom).isActive = true
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: padding.left).isActive = true
label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -padding.bottom).isActive = true
}
}
First of all, I couldn't find where you update constraints.
When you use tableView.dequeueReusableCell, you may get the old cell, so you should update old constraints (or remove old constratints and add new ones). You may read it in docs.
You should add variable to store old constraints:
var oldConstraints: [NSLayoutConstraint] = []
And should deactivate old constraints and add new constraints in setupView():
NSLayoutConstraint.deactivate(oldConstraints)
let constraints = [
imgView.topAnchor.constraint(equalTo: c.topAnchor, constant: borderSpace),
imgView.leadingAnchor.constraint(equalTo: c.leadingAnchor, constant: borderSpace),
imgView.widthAnchor.constraint(equalTo: c.widthAnchor, multiplier: 0.08),
imgView.heightAnchor.constraint(equalTo: c.widthAnchor, multiplier: 0.08),
nameLabel.topAnchor.constraint(equalTo: c.topAnchor, constant: borderSpace),
nameLabel.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 10),
nameLabel.trailingAnchor.constraint(equalTo: c.trailingAnchor, constant: -borderSpace),
commentLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: viewSpace),
commentLabel.widthAnchor.constraint(equalTo: nameLabel.widthAnchor),
commentLabel.trailingAnchor.constraint(equalTo: c.trailingAnchor, constant: -borderSpace),
commentLabel.bottomAnchor.constraint(equalTo: c.bottomAnchor)
]
NSLayoutConstraint.activate(constraints)
oldConstraints = constraints
Also you shouldn't add subviews every time you update dataObject in CommentTableViewCell, because they could have already been added.
In conclusion, you should add subview to contentView just once and update the constraints every time you update dataObject. I gave you an example how to solve your issue.
You could also use prepareForReuse() to prepare the cell for reuse:
If a UITableViewCell object is reusable—that is, it has a reuse
identifier—this method is invoked just before the object is returned
from the UITableView method dequeueReusableCellWithIdentifier:.