UItableView dynamic height depending on content - ios

How can I make the tableview cell height dynamic,
I have 1 label and 1 image in the cell, My image height is constant of 70, label height depends on the api text, if my label text I large then the image view height so table view cell should adapt label height else image View.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
below image just picking height of image

Making a UITableViewCells height dynamic based on its content is obtained by doing the following:
Make sure the content of your UITableViewCell is constrained such the dynamic content is pinned to both the top and bottom of the cell.
A contrived example cell:
class Cell: UITableViewCell {
let label = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Allows your text to expand multiple lines
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
// Constrains a UILabel to the edges of the UITableViewCells content view
label.topAnchor.constraint(equalTo: commentBox.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: commentBox.bottomAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: commentBox.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: commentBox.trailingAnchor).isActive = true
}
}
As you have above, return UITableViewAutomaticDimension from func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
To help your UITableView compute the dynamic height, its recommended to return an estimated size you think your cell is going to be.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat

Related

UITableViewCell dynamic cell heights are incorrect until scrolled

I want my UITableViewCell to expand in size when tapped.
The layout of the cell is quite straightforward. Within the UITableViewCell is a UILabel. The UILabel is constrained to the UITableViewCell with top, bottom, left and right anchors.
I also have two stored properties. labelExpandedHeightConstraint stores the UILabel's height constraint for when the label is expanded. labelCompactHeightConstraint stores the UILabel's height constraint for when the label is compacted. Notice that labelCompactHeightConstraint is initially set to active.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
let spacing = 8
self.addSubview(self.labelView)
self.labelView.translatesAutoresizingMaskIntoConstraints = false
self.labelView.topAnchor.constraint(equalTo: self.topAnchor, constant: spacing).isActive = true
self.labelView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -1 * spacing).isActive = true
self.labelView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: spacing).isActive = true
self.labelView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -1 * spacing).isActive = true
self.labelExpandedHeightConstraint = self.labelView.heightAnchor.constraint(equalToConstant: 120)
self.labelCompactHeightConstraint = self.labelView.heightAnchor.constraint(equalToConstant: 80)
self.labelCompactHeightConstraint.isActive = true
}
The expand() function below is called whenever the user taps a UITapGestureRecognizer. This function is very simple. It expands the cell by disabling labelCompactHeightConstraint and enabling labelExpandedHeightConstraint.
#objc func expand() {
self.labelCompactHeightConstraint.isActive = false
self.labelExpandedHeightConstraint.isActive = true
self.layoutIfNeeded()
}
The problem is that when the expand() function is called, the UITableViewCell and its contents do not change in size. It is not until the user scrolls the cell off the screen, and then scrolls it back onto the screen, that the size adjusts correctly.
How can I get the cell to expand immediately when tapped? I would also like this sizing change to be animated. I would really appreciate any suggestions. Thanks!
You'll need to do it differently.
Something like:
tableView.beginUpdates()
// update data for cell, or if your cell is not dynamically created - update it directly
// Usually, you'll need to update your data structures
// Reload the cell
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
From what you wrote, the place to add this code is from:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
...
}
Also, note that in most cases, you should just change the content (ie. the text in the label) and not the constraint value.
Here is a minimal full example:
class ViewController: UITableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ident", for: indexPath) as! Cell
if selections.contains(indexPath) {
cell.height.constant = 80
} else {
cell.height.constant = 10
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.beginUpdates()
if selections.contains(indexPath){
selections.remove(indexPath)
} else {
selections.insert(indexPath)
}
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
var selections = Set<IndexPath>()
}
class Cell : UITableViewCell {
#IBOutlet var height : NSLayoutConstraint!
}
Could use the table view's multiple selection but wanted to demonstrate usage of app specific data.

tableView does not appear when centered into its parent view

I have a viewController with the following (static) tableView:
class viewController: UIViewController, UITableViewDelegate {
private let tableView: UITableView = {
let tv = UITableView()
tv.separatorStyle = .singleLine
tv.allowsSelection = true
tv.isScrollEnabled = false
return tv
}()
private let tableData = ["row1", "row2", "row3", "row4"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.tableFooterView = UIView()
view.addSubview(tableView)
NSLayoutConstraints.activate([
tableView.centerXAnchor.constraints(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
tableView.centerYAnchor.constraints(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
)]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
cell.textLabel!.text = tableData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
}
When I run the app, this viewController shows a blank screen. I know that the way I am setting up the tableview's constraints is the problem because when I set up the tableView using topAnchor, bottomAnchor, leftAnchor, and rightAnchor (and with some other tweaking) the tableview appears. Any idea why the app is behaving this way?
Your table view is probably there, and centered, but you didn't define a size, so it's probably being set to zero width and height, that's why you don't see it.
You can fix this by setting a constraint on it's width and height, either to a constant or related to it's superview, depending on what you want.
The problem is this is NOT a static table view. If it were, you would not have implemented cellForRowAt. It is a normal table view and it needs a data source and delegate. Plus it needs a height and a width.

UITableViewCell with StackView not dynamically sizing height

I have a custom UITableViewCell that contains a StackView with top, bottom, leading and trailing constraints to the content view of the cell.
When I set up my tableView, I give it an estimated height and also set the rowHeight to UITableViewAutomaticDimension.
In my cellForRowAt datasource method, I dequeue the cell and then call cell.setup() which adds any given number of views to my cell's stackView.
The problem is: My cell is always being sized to the estimated height of 80p. No matter how many views I add to the cell's stackView, it all crams into 80p height. The stackView, and thus the cell, isn't growing with each new item I insert into the cell before returning it in cellForRowAt datasource method.
I tried different distribution settings for my stackView, but I'm not sure what I'm doing wrong here.
Here is a simple demonstration of adding buttons to a stack view inside an auto-sizing table view cell:
class StackOfButtonsTableViewCell: UITableViewCell {
#IBOutlet var theStackView: UIStackView!
func setup(_ numButtons: Int) -> Void {
// cells are reused, so remove any previously created buttons
theStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
for i in 1...numButtons {
let b = UIButton(type: .system)
b.setTitle("Button \(i)", for: .normal)
b.backgroundColor = .blue
b.setTitleColor(.white, for: .normal)
theStackView.addArrangedSubview(b)
}
}
}
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 6
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StackOfButtonsTableViewCell", for: indexPath) as! StackOfButtonsTableViewCell
cell.setup(indexPath.row + 1)
return cell
}
}
Assuming you have created a prototype cell, and its only content is a UIStackView configured as:
Axis: Vertical
Alignment: Fill
Distribution: Equal Spacing
Spacing: 8
and you have it constrained Top/Leading/Trailing/Bottom to the cell's content view, this is the result:
No need for any height calculations, and, since buttons do have intrinsic size, no need to set height constraints on the buttons.

Auto layout issue in TableView

I use dynamic height cells for my UITableView with this code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionsCell", for: indexPath) as! QuestionsCell
cell.label1.text = ""
cell.label2.text = ""
cell.label1.text = some text from array
cell.label2.text = some text from array
cell.label1.lineBreakMode = .byWordWrapping
cell.label1.numberOfLines = 0
cell.label1.sizeToFit()
return cell
}
Then I've pinned all constraints in Storyboard for each element (top, bottom, leading and trailing). On iPhone everything works fine, but on iPad when I scroll the UITableView this is happening (dynamic label height):
sizeToFit() only makes cell1 as big as it needs to be for the text.
You also need to use automatic height sizing for the table view cells.
In viewDidLoad,
self.tableView.rowHeight = UITableViewAutomaticDimension
You will also want to make sure you have a vertical constraint from your bold label to the "12 days ago" label, to ensure there is always vertical space between them, so they don't overlap.
you need to implement this delegate method:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
if this does not work then check if there is vertical spacing constraints between top textView and bottom textView.

Adjust height of UITableViewCell to height of UITextView

I got two cells in my UITableView. One is a custom UITableViewCell and the other is a cell with a UITextView inside and called the type TextViewCell.
Because they are static I the cells are loaded in viewDidLoad method from a xib:
textCell = Bundle.main.loadNibNamed(String(describing: TextViewCell.self),
owner: self, options: nil)?.first! as! TextViewCell
ratingCell = Bundle.main.loadNibNamed(String(describing: RatingCell.self),
owner: self, options: nil)?.first! as! RatingCell
Now I try to change the height with the the heightForRowAt delegate:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 1 {
return textCell.textView.contentSize.height
}
return ratingCell.ratingView.frame.height
}
I disabled scrolling on the UITextView but the cell is not resizing properly. In fact the cells gets smaller.
The constraints of the TextViewCell look like this:
Any suggestions?
I think you need to use self-sizing UITableViewCell. Replace your current implementation of heightForRowAt with the following:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Now height of cells in your UITableView object will be calculated automatically based on constraints.
Also, add some estimated value for row height:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0 // You can set any other value, it's up to you
}
Now you will see that the UITextView view fills the whole UITableViewCell cell.
You should get the height of UITextView in heightForRowAt and return this height as cell height. See example below
let lblDescLong = UITextView()
lblDescLong.textAlignment = .left
lblDescLong.text = “your text for text view”
lblDescLong.font = YourFont(size: 12)
let newSize = lblDescLong.sizeThatFits(CGSize(width: widthForTextView, height: CGFloat.greatestFiniteMagnitude))
return newSize.height

Resources