Я бы рекомендовал использовать UITableView.automaticDimension вместо вычисления высоты вручную и использовать шаблон делегата для обновления высоты ячейки.
Вот что я получил с этим подходом:
и пример кода:
import UIKit
struct Content {
let author: String
let date: String
let views: UInt
let recs: UInt
let content: String
}
let longText = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
"""
protocol MyCellDelegate: AnyObject {
func contentDidChange(cell: UITableViewCell)
}
final class MyCell: UITableViewCell {
static let reuseIdentifier = "MyCell"
weak var delegate: MyCellDelegate?
private var viewsCountLabel: UILabel!
private var recsLabel: UILabel!
private var authorLabel: UILabel!
private var dateLabel: UILabel!
private var contentLabel: UILabel!
private var toggleHeightButton: UIButton!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupSubviews()
}
required init?(coder: NSCoder) {
fatalError("not implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
viewsCountLabel.text = nil
recsLabel.text = nil
authorLabel.text = nil
dateLabel.text = nil
contentLabel.text = nil
contentLabel.numberOfLines = 2
}
public func setContent(_ content: Content) {
viewsCountLabel.text = "Views: \(content.views)"
recsLabel.text = "Recs: \(content.recs)"
authorLabel.text = content.author
dateLabel.text = content.date
contentLabel.text = content.content
}
private func setupSubviews() {
let leftStackView = createLeftStack()
let rightStackView = createRightStack()
let stackView = UIStackView(arrangedSubviews: [leftStackView, rightStackView])
stackView.axis = .horizontal
stackView.spacing = 5
stackView.alignment = .top
contentView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 3),
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -3)
])
}
private func createLeftStack() -> UIStackView {
let iconView = createIconView()
viewsCountLabel = createLabel()
recsLabel = createLabel()
let stackView = UIStackView(arrangedSubviews: [iconView, viewsCountLabel, recsLabel])
stackView.axis = .vertical
stackView.setCustomSpacing(4, after: iconView)
stackView.alignment = .leading
return stackView
}
private func createRightStack() -> UIStackView {
authorLabel = createLabel()
dateLabel = createLabel()
contentLabel = UILabel()
contentLabel.font = UIFont.systemFont(ofSize: 13, weight: .bold)
contentLabel.contentMode = .topLeft
toggleHeightButton = UIButton(type: .system)
toggleHeightButton.setTitle("Toggle", for: .normal)
toggleHeightButton.addTarget(self, action: #selector(expandDidTap), for: .touchUpInside)
toggleHeightButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
let stackView = UIStackView(arrangedSubviews: [authorLabel, dateLabel, contentLabel, toggleHeightButton])
stackView.axis = .vertical
stackView.alignment = .leading
return stackView
}
private func createIconView() -> UIView {
let iconView = UIView()
iconView.backgroundColor = .blue
iconView.layer.cornerRadius = 4
iconView.layer.masksToBounds = true
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 8)
label.numberOfLines = 2
label.text = "Upload Icon"
label.textAlignment = .center
label.textColor = .white
iconView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
iconView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: iconView.leadingAnchor, constant: 3),
label.trailingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: -3),
label.topAnchor.constraint(equalTo: iconView.topAnchor, constant: 3),
label.bottomAnchor.constraint(equalTo: iconView.bottomAnchor, constant: -3),
iconView.heightAnchor.constraint(equalToConstant: 40),
iconView.widthAnchor.constraint(equalToConstant: 40)
])
return iconView
}
private func createLabel() -> UILabel {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 8, weight: .light)
label.numberOfLines = 1
return label
}
@objc func expandDidTap() {
let isExpand = contentLabel.numberOfLines != 0
if isExpand {
contentLabel.numberOfLines = 0
} else {
contentLabel.numberOfLines = 2
}
delegate?.contentDidChange(cell: self)
}
}
final class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private var tableView: UITableView {
view as! UITableView
}
private var data: [Content] = [] {
didSet {
tableView.reloadData()
}
}
override func loadView() {
let tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.register(MyCell.self, forCellReuseIdentifier: MyCell.reuseIdentifier)
view = tableView
}
override func viewDidLoad() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.data = [
Content(author: "Author1", date: "15:07", views: 2, recs: 1, content: longText),
Content(author: "Author2", date: "15:07", views: 2, recs: 1, content: longText),
Content(author: "Author3", date: "15:07", views: 2, recs: 1, content: longText),
Content(author: "Author1", date: "15:07", views: 2, recs: 1, content: longText)
]
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.reuseIdentifier, for: indexPath) as! MyCell
cell.delegate = self
let content = data[indexPath.row]
cell.setContent(content)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension MyViewController: MyCellDelegate {
func contentDidChange(cell: UITableViewCell) {
UIView.animate(withDuration: 0.2) {
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
}