Dynamic Height Issue for UITableView Cells (Swift)
Asked Answered
J

26

93

Text data of variable length are being injected into tableview cell labels. In order for each cell height to be properly sized, I have implemented in viewDidLoad():

self.tableView.estimatedRowHeight = 88.0
self.tableView.rowHeight = UITableViewAutomaticDimension

This estimates the height to be 88.0 pixels and should resize the height automatically if larger. It works perfectly for cells that have yet to be scrolled to (as UITableViewAutomaticDimention is called upon scrolling to the cell), but not for the cells that are initially rendered onscreen upon loading the table with data.

I have tried reloading the data (as suggested in many other resources):

self.tableView.reloadData()

in both viewDidAppear() and viewWillAppear() and it did not help. I am lost.. does anyone know how to render the dynamic height for the cells loaded initially on screen?

Jetta answered 27/5, 2015 at 23:15 Comment(2)
Are your height constraints for the cell(s) setup correctly, i have not seen any issues with this in the wild using the Xcode 6.3 version. Even have a sample project on github with this working. github.com/darren102/DTFAutomaticCellHeightLightman
I solved this issue by overcompensating the potential height of the label. Please see my answer below. Regardless, if you have a better solution, please let me know!Jetta
C
133

Try This:

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

EDIT

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

Swift 4

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

Swift 4.2

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableView.automaticDimension
}

Define above Both Methods.
It solves the problem.

PS: Top and bottom constraints is required for this to work.

Here is example

Childhood answered 28/5, 2015 at 4:52 Comment(9)
I'll give it a try and let you know how it works out! Thanks!Jetta
@bashan It works perfectly. Make sure you have given proper constraints.Childhood
@MohamedSaleh It works perfectly, Make sure you have given proper constraints. For example i have added link.Childhood
If it's not working you just need to mess with your cell's constraints +1Gentilesse
Doesn't work. There seems to be a bug if a word is just too long but not long enough, it doesn't get rendered properly. If it is a few words its seems to work properly.Ladawnladd
@Ladawnladd try to put your string in attached exampleChildhood
This didn't work for me until I added a bottom constraint. I think it's worth noting in your answer that top and bottom constraints must be set for this to work.Semipro
Bottom and top contraints is required for this to work.Bartolomeo
Bottom and top constraints for the "cell elements" is required for this to workGonzales
A
48

Use this:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 300

and don't use: heightForRowAtIndexPath delegate function

Also, in the storyboard don't set the height of the label that contains a large amount of data. Give it top, bottom, leading, trailing constraints.

Almira answered 11/6, 2016 at 3:8 Comment(4)
Bottom-constraint is important! I missed that and wondered why the height could not be calculated.Virnelli
I was leaving behind this heightForRowAtIndexPathThuythuya
@Javeed I think you can use it anywhere like Static cell or Dynamic Cell..Almira
I don't know why this worked instead of delegate functions. However, thanks.Wive
P
19

SWIFT 3

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160

AND!!! In storyBoard: You HAVE TO set TOP & BOTTOM constraints for your Label. Nothing else.

Ptolemaic answered 10/1, 2017 at 19:36 Comment(0)
J
16

This strange bug was solved through Interface Builder parameters as the other answers did not resolve the issue.

All I did was make the default label size larger than the content potentially could be and have it reflected in the estimatedRowHeight height too. Previously, I set the default row height in Interface Builder to 88px and reflected it like so in my controller viewDidLoad():

self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 88.0

But that didn't work. So I realized that content wouldn't ever become larger than maybe 100px, so I set the default cell height to 108px (larger than the potential content) and reflected it like so in the controller viewDidLoad():

self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 108.0

This actually allowed the code to shrink down the initial labels to the correct size. In other words, it never expanded out to a larger size, but could always shrink down... Also, no additional self.tableView.reloadData() was needed in viewWillAppear().

I know this does not cover highly variable content sizes, but this worked in my situation where the content had a maximum possible character count.

Not sure if this is a bug in Swift or Interface Builder but it works like a charm. Give it a try!

Jetta answered 29/5, 2015 at 15:35 Comment(1)
I have also same problem. I have update my answer. it solved my problem. just define only this method and remove all stuff related to height.Childhood
A
13

Set automatic dimension for row height & estimated row height and ensure following steps:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Set automatic dimensions for row height
    // Swift 4.2 onwards
    table.rowHeight = UITableView.automaticDimension
    table.estimatedRowHeight = UITableView.automaticDimension


    // Swift 4.1 and below
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension

}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    // Swift 4.2 onwards
    return UITableView.automaticDimension

    // Swift 4.1 and below
    return UITableViewAutomaticDimension
}

For Example: if you have a label in your UITableviewCell then,

  • Set number of lines = 0 (& line break mode = truncate tail)
  • Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
  • Optional: Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.

Here is sample label with dynamic height constraints.

enter image description here

Allure answered 13/10, 2017 at 12:12 Comment(1)
The entry in ViewDidLoad() as mentioned by Krunal was the solution for me.Mesosphere
J
9

For Swift 3 you can use the following:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}
Jasmin answered 18/1, 2017 at 7:58 Comment(1)
"UITableViewAutomaticDimension" has been deprecated and in swift 5, it is "UITableView.automaticDimension"Dedicate
S
8

Dynamic sizing cell of UITableView required 2 things

  1. Setting the the right constraint of your view inside the table view cell (mostly it includes giving your view proper top , bottom and traling constraints)
  2. Calling these properties of TableView in viewDidLoad()

     tableView.rowHeight = UITableViewAutomaticDimension
    
     tableView.estimatedRowHeight = 140
    

This is a wonderfull tutorial on self-sizing (dynamic table view cells) written in swift 3 .

Sutler answered 3/4, 2017 at 7:34 Comment(0)
D
5

In addition to what others have said,

SET YOUR LABEL'S CONSTRAINTS RELATIVE TO THE SUPERVIEW!

So instead of placing your label's constraints relative to other things around it, constrain it to the table view cell's content view.

Then, make sure your label's height is set to more than or equal 0, and the number of lines is set to 0.

Then in ViewDidLoad add:

tableView.estimatedRowHeight = 695

tableView.rowHeight = UITableViewAutomaticDimension
Deoxyribose answered 20/6, 2018 at 20:28 Comment(1)
SET YOUR LABEL'S CONSTRAINTS RELATIVE TO THE SUPERVIEW!: SAVED MY HOURS !! thank you.Fils
P
5

In my case - In storyboard i had a two labels as in image below, both labels was having desired width values been set before i made it equal. once you unselect, it will change to automatic, and as usual having below things should work like charm.

1.rowHeight = UITableView.automaticDimension, and
2.estimatedRowHeight = 100(In my case).
3.make sure label number of lines is zero.

enter image description here

Passant answered 19/7, 2019 at 11:42 Comment(0)
R
4

To make autoresizing of UITableViewCell to work make sure you are doing these changes :

  • In Storyboard your UITableView should only contain Dynamic Prototype Cells (It shouldn't use static cells) otherwise autoresizing won't work.
  • In Storyboard your UITableViewCell's UILabel has configured for all 4 constraints that is top, bottom, leading and trailing constraints.
  • In Storyboard your UITableViewCell's UILabel's number of lines should be 0
  • In your UIViewController's viewDidLoad function set below UITableView Properties :

    self.tableView.estimatedRowHeight = <minimum cell height> 
    self.tableView.rowHeight = UITableViewAutomaticDimension
    
Regardant answered 1/6, 2017 at 20:50 Comment(2)
How to implement same using Static cells?Earleanearleen
After trying all solutions offered here, only this one (setting the <minimum cell height> for estimatedRowHeight) truly fixed my problem. Before this, the cell did autoresize, but I also was getting various constraint warnings. Now I don't! So, thank you very much, Anshul!!Lethia
S
4

For Swift i checked this answer in iOS 9.0 and iOS 11 also (Xcode 9.3)

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

Here you need to add top, bottom, right and left constraints

Sometime answered 29/5, 2018 at 9:10 Comment(0)
W
4

For Swift 4.2

@IBOutlet weak var tableVw: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Set self as tableView delegate
    tableVw.delegate = self

    tableVw.rowHeight = UITableView.automaticDimension
    tableVw.estimatedRowHeight = UITableView.automaticDimension
}

// UITableViewDelegate Method 
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

    return UITableView.automaticDimension
}

Happy Coding :)

Weitzel answered 20/2, 2019 at 6:51 Comment(0)
T
4

This is simple when doing 2 things:

  1. setting the automatic height
tableView.rowHeight = UITableView.automaticDimension
  1. creating all TableViewCells with FULL constraints from top to bottom. The last element MUST define some bottom spacing to end the cell.

So the layout engine can compute the cell heigth and apply the value correctly.

Trygve answered 29/8, 2019 at 10:18 Comment(0)
P
3

Unfortunately, I am not sure what I was missing. The above methods don't work for me to get the xib cell's height or let the layoutifneeded()or UITableView.automaticDimension to do the height calculation. I've been searching and trying for 3 to 4 nights but could not find an answer. Some answers here or on another post did give me hints for the workaround though. It's a stupid method but it works. Just add all your cells into an Array. And then set the outlet of each of your height constraint in the xib storyboard. Finally, add them up in the heightForRowAt method. It's just straight forward if you are not familiar with the those APIs.

Swift 4.2

CustomCell.Swift

@IBOutlet weak var textViewOneHeight: NSLayoutConstraint!
@IBOutlet weak var textViewTwoHeight: NSLayoutConstraint!
@IBOutlet weak var textViewThreeHeight: NSLayoutConstraint!

@IBOutlet weak var textViewFourHeight: NSLayoutConstraint!
@IBOutlet weak var textViewFiveHeight: NSLayoutConstraint!

MyTableViewVC.Swift

.
.
var myCustomCells:[CustomCell] = []
.
.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = Bundle.main.loadNibNamed("CustomCell", owner: self, options: nil)?.first as! CustomCell

.
.
myCustomCells.append(cell)
return cell

}


override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

   let totalHeight = myCustomCells[indexPath.row].textViewOneHeight.constant + myCustomCells[indexPath.row].textViewTwoHeight.constant +  myCustomCells[indexPath.row].textViewThreeHeight.constant + myCustomCells[indexPath.row].textViewFourHeight.constant + myCustomCells[indexPath.row].textViewFiveHeight.constant

  return totalHeight + 40 //some magic number


}
Planter answered 11/3, 2019 at 16:42 Comment(0)
D
3

I use these

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

    return 100
}
Diver answered 11/3, 2019 at 17:15 Comment(0)
L
2

Try

override func viewWillAppear(animated: Bool) {
    self.tableView.layoutSubviews()
}

I had the same problem and it works for me.

Laroy answered 15/4, 2016 at 14:26 Comment(2)
I'll give it a try and let you know what happens. Thanks for the suggestion! Almost a year after asking, I haven't found any other answer besides my own! Hope your's works!Jetta
make sure to call super.viewWillAppear(animated) before you call layoutsubviewsSheathbill
A
2

You should just set all constraints for TOP, BOTTOM and HEIGHT for each object on cell view/views and remove exists middle Y position if have. Because where you didn't this, puts artifacts on another views.

Aret answered 26/6, 2017 at 14:58 Comment(2)
Hi!, This answer might be good, but It's better to provide a working example, that explains and demonstrates your answer.Powel
this is my case, I made cell using snapkit. and the cell has a label ``` swift titleLabel.snp.makeConstraints { make in make.leading.equalTo(circleView.snp.trailing).offset(5) make.trailing.equalToSuperview().offset(-17.5) // change make.top.bottom.equalToSuperview() } ``` greate answer for me. thanks Gennady~Newly
S
2

For objective c this is one of my nice solution. it's worked for me.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    cell.textLabel.text = [_nameArray objectAtIndex:indexPath.row];
    cell.textLabel.numberOfLines = 0;
    cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return UITableViewAutomaticDimension;
}

We need to apply these 2 changes.

1)cell.textLabel.numberOfLines = 0;
  cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;

2)return UITableViewAutomaticDimension;
Sometime answered 8/3, 2018 at 5:25 Comment(0)
R
2
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 88.0

And don't forget to add botton constraints for label

Rockfish answered 29/8, 2019 at 10:57 Comment(0)
O
1

I had also got this issue initially, I had resolved my issue from this code try avoiding the use of self.tableView.reloadData() instead of this code for dynamic height

[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
Onepiece answered 28/5, 2015 at 5:13 Comment(0)
C
1

I was just inspired by your solution and tried another way.

Please try to add tableView.reloadData() to viewDidAppear().

This works for me.

I think the things behind scrolling is "the same" as reloadData. When you scroll the screen, it's like calling reloadData() when viewDidAppear .

If this works, plz reply this answer so I could be sure of this solution.

Carman answered 23/3, 2016 at 17:40 Comment(2)
I'll give it a try and update to see if it creates the same solution. Thanks!Jetta
Sorry Kang, strangely it had no effect on the layout. It left them as the static value they were set as in Interface Builder rather than shrink wrapping them to the proper size.Jetta
S
1

When using a static UITableView, I set all the values in the UILabels and then call tableView.reloadData().

Scarper answered 26/8, 2019 at 13:52 Comment(0)
M
1

What worked for me was creating a height constraint on my custom cell that I set at runtime (I've got an expand/collapse button in each cell).

Then in heightForRowAt in the parent, I had to do a combination of suggested answers:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if let cell = tableView.cellForRow(at: indexPath) as? GroupTableViewCell {
        return cell.heightConstraint.constant
    }
    return UITableView.automaticDimension
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return 88.0
}

I use the already calculated height constraint constant where it's available and UITableView.automaticDimension otherwise. This was the only way to get the correct height and maintain the correct cell state when the cell gets recycled.

I hear it's considered bad practice to reference the cell itself inside heightForRowAt, but I don't see another way of doing it with custom cell objects with dynamic heights whilst keeping all constraints satisfied.

Mckenney answered 12/10, 2022 at 10:41 Comment(0)
T
-1
self.Itemtableview.estimatedRowHeight = 0;
self.Itemtableview.estimatedSectionHeaderHeight = 0;
self.Itemtableview.estimatedSectionFooterHeight = 0;


[ self.Itemtableview reloadData];
self.Itemtableview.frame = CGRectMake( self.Itemtableview.frame.origin.x,  self.Itemtableview.frame.origin.y,  self.Itemtableview.frame.size.width,self.Itemtableview.contentSize.height + self.Itemtableview.contentInset.bottom + self.Itemtableview.contentInset.top);
Tamper answered 11/7, 2018 at 6:28 Comment(1)
While this code may solve the question, including an explanation of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please edit your answer to add explanations and give an indication of what limitations and assumptions apply.Sepaloid
S
-2

Set proper constraint and update delegate methods as:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

This will resolve dynamic cell height issue. IF not you need to check constraints.

Skat answered 10/2, 2017 at 11:15 Comment(3)
Same code as Ahmed Lotfy's answer on this same page...Runlet
Hmmm, I have checked for swift 3.*, I missed Ahmed ansSkat
you have to provide the estimated row height as well for the code to workWilden
N
-2

Swift 5 Enjoy

tablev.rowHeight = 100
tablev.estimatedRowHeight = UITableView.automaticDimension


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = self.tablev.dequeueReusableCell(withIdentifier: "ConferenceRoomsCell") as! ConferenceRoomsCell
    cell.lblRoomName.numberOfLines = 0
    cell.lblRoomName.lineBreakMode = .byWordWrapping
    cell.lblRoomName.text = arrNameOfRooms[indexPath.row]
    cell.lblRoomName.sizeToFit()
    return cell
}
Nikolos answered 3/11, 2020 at 9:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.