I'm trying to create a custom UItableViewCell with three labels. Two of them on the left side (like the built-in title + subtitle layout) and the other one at the right side. I'm laying out the labels programatically.
class CustomCell: UITableViewCell {
static let identifier = String(describing: self)
lazy var primaryLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .body)
return label
}()
lazy var secondaryLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .footnote)
return label
}()
lazy var tertiaryLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .callout)
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
contentView.addSubview(primaryLabel)
contentView.addSubview(secondaryLabel)
contentView.addSubview(tertiaryLabel)
NSLayoutConstraint.activate([
tertiaryLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
tertiaryLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
tertiaryLabel.heightAnchor.constraint(equalToConstant: 18),
primaryLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
primaryLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
primaryLabel.heightAnchor.constraint(equalToConstant: 19),
secondaryLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
secondaryLabel.topAnchor.constraint(equalTo: primaryLabel.bottomAnchor, constant: 8),
secondaryLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 12),
// secondaryLabel.heightAnchor.constraint(equalToConstant: 15)
])
}
}
I want the cell's height to be calculated dynamically. I set the rowHeight and estimatedRowHeight property values to UITableView.automaticDimension in the view controller when I instantiate the table view.
private lazy var tableView: UITableView = {
let tableView = UITableView(frame: view.bounds, style: .grouped)
tableView.allowsSelection = false
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = UITableView.automaticDimension
tableView.register(CustomCell.self, forCellReuseIdentifier: CustomCell.identifier)
return tableView
}()
But the cell still shows up like this. With the default height. Even though I have auto layout constraints laid out from top to bottom. I get no warnings in the console either.
Any idea what I am missing here?
Demo project
Two observations:
Your secondary label needs a negative constant to the bottom of the container.
Also, your estimated row height should be some reasonable fixed value (e.g 44 or something like that ... it doesn't need to be perfect, but just some reasonable value that the table can use for estimating the height of rows that haven't been presented yet). You do not want to use UITableView.automaticDimension for estimatedRowHeight, only for rowHeight.
Related
I have been trying to scroll the view for a whole day yesterday and I am not able to figure out why it won't scroll. I am not sure what I am doing wrong !!
I have looked at the solutions on stackoverflow:
UIScrollView Scrollable Content Size Ambiguity
How to append a character to a string in Swift?
Right anchor of UIScrollView does not apply
But still, the view doesn't scroll and the scrollview height should be equal to the conrainerView height. But in my case, it stays fixed to the height of the view.
Here is the code repo: https://bitbucket.org/siddharth_shekar/ios_colttestproject/src/master/
Kindly go through and any help would be appreciated. Thanks!!
Here is the code Snippet as well, If you want to go through the constraints and see if there is anything I have added which is not letting the scroll view do its thing !!
I have made changes to the view just one looong text and removed other images, labels, etc to produce the minimal reproducable code.
And I looked at this persons project as well. Their view scrolls!!
https://useyourloaf.com/blog/easier-scrolling-with-layout-guides/
I am just not sure what I am doing differently!!!!
Here is my code for the contentView. It is literally just a textlabel
import Foundation
import UIKit
class RecipeUIView: UIView{
private var recipeTitle: UILabel! = {
let label = UILabel()
label.numberOfLines = 0
label.font = .systemFont(ofSize: 24, weight: .bold)
label.textColor = .gray
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.adjustsFontForContentSizeCategory = true
return label
}()
func setupView(currentRecipe: Receipe?){
recipeTitle.text = currentRecipe?.dynamicTitle
addSubview(recipeTitle)
let margin = readableContentGuide
// Constraints
recipeTitle.topAnchor.constraint(equalTo: margin.topAnchor, constant: 4).isActive = true
recipeTitle.leadingAnchor.constraint(equalTo: margin.leadingAnchor, constant: 20).isActive = true
recipeTitle.trailingAnchor.constraint(equalTo: margin.trailingAnchor, constant: -20).isActive = true
}
}
And here is the viewController
import Foundation
import UIKit
class RecipeViewController: UIViewController {
var selectedRecipe: Receipe?
let recipeView: RecipeUIView = {
let view = RecipeUIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
recipeView.setupView(currentRecipe: selectedRecipe)
recipeView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
view.addSubview(scrollView)
scrollView.addSubview(recipeView)
let frameGuide = scrollView.frameLayoutGuide
let contentGuide = scrollView.contentLayoutGuide
// Scroll view layout guides (iOS 11)
NSLayoutConstraint.activate([
frameGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor),
frameGuide.topAnchor.constraint(equalTo: view.topAnchor),
frameGuide.trailingAnchor.constraint(equalTo: view.trailingAnchor),
frameGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor),
contentGuide.leadingAnchor.constraint(equalTo: recipeView.leadingAnchor),
contentGuide.topAnchor.constraint(equalTo: recipeView.topAnchor),
contentGuide.trailingAnchor.constraint(equalTo: recipeView.trailingAnchor),
contentGuide.bottomAnchor.constraint(equalTo: recipeView.bottomAnchor),
contentGuide.widthAnchor.constraint(equalTo: frameGuide.widthAnchor),
])
}
}
And I am still not able to scroll the view. Here is a screenshot of my project output. Still no scroll guide lines on the right!!
UPDATE:: Now the text scrolls, but when I add a UITableView in the UIView the scrolling works but the tableView is not seen in the UiView.
Is it due to the constraints again???
here is the code for the same::
class RecipeUIView: UIView, UITableViewDelegate, UITableViewDataSource{
var currentRecipe: Receipe?
private let tableView: UITableView = {
let tableView = UITableView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
tableView.backgroundColor = .green
return tableView
}()
private var recipeTitle: UILabel! = {
let label = UILabel()
label.numberOfLines = 0
label.font = .systemFont(ofSize: 24, weight: .bold)
label.textColor = .gray
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.adjustsFontForContentSizeCategory = true
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("++++ IngrediantsTableViewCell tableview count: \(currentRecipe?.ingredients.count ?? 0)")
return currentRecipe?.ingredients.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("++++ IngrediantsTableViewCell tableview cellForRow ")
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
cell.textLabel!.text = "\(currentRecipe?.ingredients[indexPath.row].ingredient ?? "")"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//return UITableView.automaticDimension
return 30
}
func setupView(currentRecipe: Receipe?){
let margin = readableContentGuide
self.currentRecipe = currentRecipe
recipeTitle.text = currentRecipe?.dynamicTitle
addSubview(recipeTitle)
// Constraints
recipeTitle.topAnchor.constraint(equalTo: margin.topAnchor, constant: 4).isActive = true
recipeTitle.leadingAnchor.constraint(equalTo: margin.leadingAnchor, constant: 20).isActive = true
recipeTitle.trailingAnchor.constraint(equalTo: margin.trailingAnchor, constant: -20).isActive = true
addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: recipeTitle.bottomAnchor, constant: 10).isActive = true
tableView.leadingAnchor.constraint(equalTo: margin.leadingAnchor, constant: 20).isActive = true
tableView.trailingAnchor.constraint(equalTo: margin.trailingAnchor, constant: -20).isActive = true
tableView.bottomAnchor.constraint(equalTo: margin.bottomAnchor, constant: -20).isActive = true
tableView.reloadData()
}
}
You are missing a constraint...
In your RecipeUIView class, you have this:
func setupView(currentRecipe: Receipe?){
recipeTitle.text = currentRecipe?.dynamicTitle
addSubview(recipeTitle)
let margin = readableContentGuide
// Constraints
recipeTitle.topAnchor.constraint(equalTo: margin.topAnchor, constant: 4).isActive = true
recipeTitle.leadingAnchor.constraint(equalTo: margin.leadingAnchor, constant: 20).isActive = true
recipeTitle.trailingAnchor.constraint(equalTo: margin.trailingAnchor, constant: -20).isActive = true
}
So, you have no constraint controlling the view's Height.
Add this line:
recipeTitle.bottomAnchor.constraint(equalTo: margin.bottomAnchor, constant: -20).isActive = true
And you'll get vertical scrolling.
Two side notes...
First, in ``RecipeViewController`, change your constraints like this:
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
recipeView.leadingAnchor.constraint(equalTo: contentGuide.leadingAnchor),
recipeView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
recipeView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor),
recipeView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor),
recipeView.widthAnchor.constraint(equalTo: frameGuide.widthAnchor),
])
There's no real functional difference, but it is more logical and more readable to think in terms of:
I'm constraining the scrollView to the view
I'm constraining the recipeView to the scroll view's .contentLayoutGuide (which determines the "scrollable" size)
I'm constraining the recipeView width the the scroll view's .frameLayoutGuide
Second, giving views contrasting background colors can be very helpful when trying to debug layouts.
For example, if I set background colors like this:
recipeTitle label : cyan
recipeView : yellow
scrollView : orange
It looks like this when running (with your original constraints):
Since the cyan label is a subview of the yellow view, it is obvious that the yellow view height is not correct.
After add the missing bottom constraint, it looks like this:
I've created a custom UITableViewCell class as shown below (programmatically - no use of storyboards for this):
import UIKit
class MainGroupCell: UITableViewCell {
var groupLabel : UILabel {
let label = UILabel()
label.textColor = .black
label.text = "Test Group"
label.font = UIFont(name: "candara", size: 20)
return label
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(groupLabel)
groupLabel.snp.makeConstraints({make in
make.center.equalTo(self.contentView)
})
}
required init?(coder aDecoder: NSCoder){
fatalError("init(coder:) has not been implemented")
}
}
And for some reason, I'm hitting the error that contentView and groupLabel are not in the same view hierarchy, but they are - I've added groupLabel as a subview to contentView as you can see. Any reason for hitting this error? I gave it a shot with regular Atuolayout API as well, instead of SnapKit, and no such luck. Feel like this might be a small mistake I'm missing. I've also attempted the equalToSuperview constraint, rather than what I have shown above, but as expected it also throws the same error - groupLabel's superview returns nil.
Error:
Unable to activate constraint with anchors <NSLayoutXAxisAnchor:0x280870b80
"UILabel:0x105e79fa0'Test Group'.centerX"> and <NSLayoutXAxisAnchor:0x280870a00
"UITableViewCellContentView:0x105fa16a0.centerX"> because they have no common ancestor.
Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
Try this ,
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
addSubview(groupLabel)
groupLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
groupLabel.leadingAnchor.constraint(equalTo: leadingAnchor,constant: 16),
groupLabel.topAnchor.constraint(equalTo: topAnchor, constant: 16),
groupLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
groupLabel.bottomAnchor.constraint(equalTo: bottomAnchor,constant: -16),
])
}
change your grouplabel like this
let groupLabel : UILabel = {
let label = UILabel()
label.textColor = .black
label.text = "Test Group"
label.font = UIFont(name: "candara", size: 20)
return label
}()
Change to
make.center.equalTo(self.contentView.snp.center)
or
make.center.equalToSuperview()
Instead of
make.center.equalTo(self.contentView)
I'm creating a simple app. I'm trying to make two screens look the same but I can't seem to get one of the table views to work. Instead, when the set up table view function is called, the error signal sigbart appears. I can't see why this is since on the other screen the table view works no problem and I've copied over the code.
let tableview: UITableView = {
let tv = UITableView()
tv.backgroundColor = UIColor.white
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
func setupTableView() {
tableview.delegate = self
tableview.dataSource = self
tableview.register(BunchCells.self, forCellReuseIdentifier: "cellId")
tableview.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0)
view.addSubview(tableview)
NSLayoutConstraint.activate([
tableview.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 170),
tableview.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
tableview.rightAnchor.constraint(equalTo: self.view.rightAnchor),
tableview.leftAnchor.constraint(equalTo: self.view.leftAnchor)
])
}
class BunchCells: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let cellView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
view.layer.cornerRadius = 10
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let dayLabel: UILabel = {
let label = UILabel()
label.text = "Day 1"
label.textColor = UIColor.white
label.font = UIFont.boldSystemFont(ofSize: 16)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setupView() {
addSubview(cellView)
NSLayoutConstraint.activate([
cellView.topAnchor.constraint(equalTo: self.topAnchor, constant: 20),
cellView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10),
cellView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 10),
cellView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
dayLabel.heightAnchor.constraint(equalToConstant: 200).isActive = true
dayLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
dayLabel.centerYAnchor.constraint(equalTo: cellView.centerYAnchor).isActive = true
dayLabel.leftAnchor.constraint(equalTo: cellView.leftAnchor, constant: 20).isActive = true
}
}
I run your program and doesn't have any error, but I think this looks a little bit weird:
weak var tableView: UITableView!
let tableview: UITableView = {
let tv = UITableView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.separatorColor = UIColor.white
return tv
}()
Maybe cause of your error was what you accidentally invoke method on tableView which is always nil.? If it is not, give some hints how to reproduce your error).
I have a simple UITableViewCell subclass in which I have a titleLabel property (the cell has more views, but for the sake of showing the issue, I will only do one label as it also breaks).
Here is my label code:
self.titleLabel = UILabel(frame: .zero)
self.titleLabel.numberOfLines = 0
self.titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
self.titleLabel.textColor = UIColor.white
self.titleLabel.adjustsFontSizeToFitWidth = false
self.titleLabel.textAlignment = .left
self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.titleLabel)
self.titleLabel.topAnchor.constraint(equalTo: self.artworkImageView.topAnchor).isActive = true
self.titleLabel.leftAnchor.constraint(equalTo: self.artworkImageView.rightAnchor, constant: 10.0).isActive = true
self.titleLabel.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -10.0).isActive = true
self.titleLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor).isActive = true
I also set my UITableView up like this:
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 50.0
However it keeps breaking constraints with an error like this:
"<NSLayoutConstraint:0x28211ce10 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x10859a4f0.height == 4.33333 (active)>"
There are more constraints, however this one says that my cell content view is only 4.3 of height, however I want it to grow as the label grows.
I also tried setting contentHuggingPriorities and the priority of the bottom anchor. I also compared it to code online or IB constraints I saw online and they all set 4 constraints: top, left, bottom, right.
I also tried leading and trailing instead of left and right - same result.
Any help appreciated
Here is my full AlbumTableViewCell:
class AlbumTableViewCell: UITableViewCell {
public private(set) var artworkImageView: UIImageView
public private(set) var titleLabel: UILabel
public private(set) var albumInfoLabel: UILabel
public private(set) var artistNameLabel: UILabel
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
self.artworkImageView = UIImageView(frame: .zero)
self.titleLabel = UILabel(frame: .zero)
self.albumInfoLabel = UILabel(frame: .zero)
self.artistNameLabel = UILabel(frame: .zero)
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.tintColor = UIColor.white
self.backgroundColor = UIColor.clear
self.contentView.backgroundColor = UIColor.barTintColor
self.contentView.layer.masksToBounds = false
self.contentView.layer.cornerRadius = 10.0
self.artworkImageView.layer.cornerRadius = 10.0
self.artworkImageView.layer.masksToBounds = true
self.artworkImageView.contentMode = .scaleAspectFit
self.artworkImageView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.artworkImageView)
// image view
self.artworkImageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 5).isActive = true
self.artworkImageView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 5).isActive = true
self.artworkImageView.widthAnchor.constraint(equalToConstant: 80).isActive = true
self.artworkImageView.heightAnchor.constraint(equalToConstant: 80).isActive = true
self.titleLabel = UILabel(frame: .zero)
self.titleLabel.numberOfLines = 2
self.titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
self.titleLabel.textColor = UIColor.white
self.titleLabel.adjustsFontSizeToFitWidth = false
self.titleLabel.textAlignment = .left
self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.titleLabel)
// title
self.titleLabel.leadingAnchor.constraint(equalTo: self.artworkImageView.trailingAnchor, constant: 5.0).isActive = true
self.titleLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 5.0).isActive = true
self.titleLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -5.0).isActive = true
self.titleLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true
self.albumInfoLabel.numberOfLines = 1
self.albumInfoLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
self.albumInfoLabel.textColor = UIColor.lightGray
self.albumInfoLabel.adjustsFontSizeToFitWidth = true
self.albumInfoLabel.textAlignment = .left
self.albumInfoLabel.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.albumInfoLabel)
// albumInfoLabel
self.albumInfoLabel.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: 5.0).isActive = true
self.albumInfoLabel.leadingAnchor.constraint(equalTo: self.titleLabel.leadingAnchor).isActive = true
self.albumInfoLabel.trailingAnchor.constraint(equalTo: self.titleLabel.trailingAnchor).isActive = true
self.albumInfoLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true
self.artistNameLabel = UILabel(frame: .zero)
self.artistNameLabel.numberOfLines = 1
self.artistNameLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
self.artistNameLabel.textColor = UIColor.lightGray
self.artistNameLabel.adjustsFontSizeToFitWidth = true
self.artistNameLabel.textAlignment = .left
self.artistNameLabel.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.artistNameLabel)
// albumInfoLabel
self.artistNameLabel.topAnchor.constraint(equalTo: self.albumInfoLabel.bottomAnchor, constant: 5.0).isActive = true
self.artistNameLabel.leadingAnchor.constraint(equalTo: self.albumInfoLabel.leadingAnchor).isActive = true
self.artistNameLabel.trailingAnchor.constraint(equalTo: self.albumInfoLabel.trailingAnchor).isActive = true
self.artistNameLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true
let selectedView: UIView = UIView(frame: .zero)
selectedView.backgroundColor = UIColor.gray
selectedView.layer.cornerRadius = 10.0
selectedView.layer.masksToBounds = false
self.selectedBackgroundView = selectedView
}
override func layoutSubviews() {
super.layoutSubviews()
let contentViewFrame = self.contentView.frame
let insetContentViewFrame = contentViewFrame.inset(by: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))
self.contentView.frame = insetContentViewFrame
self.selectedBackgroundView?.frame = insetContentViewFrame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This code does not crash anymore but the cell does not autoresize (see image).. The light gray area is the content view
this code does not break any constrains anymore, but the cell also does not calculate the hight automatically. Here is my table view controller:
self.tableView.register(AlbumTableViewCell.self, forCellReuseIdentifier: "AlbumCell")
self.tableView.separatorStyle = .none
self.tableView.tableFooterView = UIView(frame: .zero)
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 50.0
var titleLabel = UILabel()
contentView.addSubview(titleLabel)
titleLabel.textColor = UIColor(red:0.32, green:0.17, blue:0.12, alpha:1.0)
titleLabel.font = UIFont.boldSystemFont(ofSize: 16.0)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
titleLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 8).isActive = true
titleLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
titleLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 8).isActive = true
Try this.
To be honest i don't know exactly what's your issue but i know for sure that you have a bad time with cell constraint and you want a dynamic cell .
So let's say you have a cell with 4 views artWrokImageView , artNameLabel , artDescriptionLabel and the artistNameLabel
First you need to make sure these views most constraint from top and bottom the table cell , So when you call self.tableView.rowHeight = UITableView.automaticDimension it knows how to dynamically expand .
Second you need to tell the table to expand when ever view did appear
This is demo for the 4 views above .
Table View Controller :
class YourTableViewController : UITableViewController {
let customTableCellID = "customTableCellID";
override func viewDidLoad() {
super.viewDidLoad();
setupTable();
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tableView.estimatedRowHeight = 50;
self.tableView.rowHeight = UITableView.automaticDimension;
}
fileprivate func setupTable() {
tableView.register(YourCustomTableCell.self, forCellReuseIdentifier: customTableCellID);
}
}
extension YourTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1;
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1;
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: customTableCellID, for: indexPath) as! YourCustomTableCell
cell.artistNameLabel.text = "the real art";
cell.artworkImageView.image = UIImage(named: "mazen");
cell.artDescriptionLabel.text = "long long long long long long long long long long long long long long long long long long long long long long long long long description";
cell.artNameLabel.text = "someting"
return cell
}
}
Cell :
class YourCustomTableCell : UITableViewCell {
var artworkImageView : UIImageView = {
let imageView = UIImageView();
imageView.translatesAutoresizingMaskIntoConstraints = false;
return imageView;
}()
var artNameLabel : UILabel = {
let label = UILabel();
label.font = UIFont.boldSystemFont(ofSize: 20);
label.translatesAutoresizingMaskIntoConstraints = false;
return label
}()
var artDescriptionLabel : UILabel = {
let label = UILabel();
label.textColor = .darkGray;
label.numberOfLines = 0;
label.translatesAutoresizingMaskIntoConstraints = false;
return label;
}()
var artistNameLabel : UILabel = {
let label = UILabel();
label.textColor = .blue;
label.translatesAutoresizingMaskIntoConstraints = false;
return label;
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier);
setupCell();
}
fileprivate func setupCell() {
// add views
contentView.addSubview(artworkImageView);
contentView.addSubview(artNameLabel);
contentView.addSubview(artDescriptionLabel);
contentView.addSubview(artistNameLabel);
// layout views
// image view
artworkImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true;
artworkImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true;
artworkImageView.widthAnchor.constraint(equalToConstant: 80).isActive = true;
artworkImageView.heightAnchor.constraint(equalToConstant: 80).isActive = true;
// art name
artNameLabel.leadingAnchor.constraint(equalTo: artworkImageView.trailingAnchor, constant: 5).isActive = true;
artNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true;
artNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true;
artNameLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true;
// descripion
artDescriptionLabel.leadingAnchor.constraint(equalTo: artworkImageView.trailingAnchor, constant: 5).isActive = true;
artDescriptionLabel.topAnchor.constraint(equalTo: artNameLabel.bottomAnchor, constant: 5).isActive = true;
artDescriptionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true;
// in art description label don't set the height anchors so it can expand
// artist name
artistNameLabel.leadingAnchor.constraint(equalTo: artworkImageView.trailingAnchor, constant: 5).isActive = true;
artistNameLabel.topAnchor.constraint(equalTo: artDescriptionLabel.bottomAnchor, constant: 5).isActive = true;
artistNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true;
artistNameLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true; // this constraint is requierd for dynamic cell
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
And if this answer not in your case please tell me .
Your tableviewcells don't know what their height is suppose to be. Which is fine.
You just have to give enough constraints so that it can figure it out. You're not doing that!
The major issue I currently see is that the artworkImageView is not constrained to top and bottom. Every vertical axiom of views needs to be constrained top to bottom.
Your first vertical axiom is just the image. It doesn't have a bottom constraint. Add that. So the tableviewcell knows how much it needs to resize itself. I strongly recommend you to see this moment of WWDC.
Also earlier in the same video at this moment it strongly recommend that you just dump your views in multiple stackviews and organize it that way. So that's also an alternative.
PS:
don't dump self. It just increases the line width with no added benefit.
Move all your non-layout related setup of your labels/images to their own instantiation. e.g.
lazy label : UILabel = {
let label = UILabel()
label.text = "John"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
No need to mention frame : .zero. Just UILabel() implies the frame is zero.
It's always best to start your UI small then keep adding more elements to it. That way debugging your layout becomes easier/smaller.
I have a tableview cell with a textview inside it:
class DetailsCell: UITableViewCell, UITextViewDelegate {
let detailsTextView: UITextView = {
let tv = UITextView()
tv.font = UIFont(name: "AvenirNext-Medium", size: 24)
tv.isScrollEnabled = false
tv.textColor = .white
tv.backgroundColor = .clear
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
func textViewDidChange(_ textView: UITextView) {
let addTodoViewController = AddTodoViewController()
addTodoViewController.begindEndUpdate()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: "DetailsCell")
addSubview(detailsTextView)
detailsTextView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
detailsTextView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
detailsTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
detailsTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
backgroundColor = .black
textLabel?.isHidden = true
detailsTextView.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When I load the app and the textview already has a text, assigned programmatically, the cell properly get's the right height and the textview is properly resized, but when I try to change the text of the textview, the cell and the textview don't resize. Can someone help me?
override func viewWillAppear(_ animated: Bool) {
toDoTextField.becomeFirstResponder()
addTodoTableView.estimatedRowHeight = 60
addTodoTableView.rowHeight = UITableViewAutomaticDimension
}
Textview with not initial text Image
Textview with initial text Image
UITextView does not resize itself when its content changes. You have to write some code to resize your textView and then accordingly adjust the height of your cell. Probably this can help you resizing textview to its content
or you can use some third party library instead of default UITextView like this one GrowingTextView