How to resize custom cell height dynamically - ios

I want to resize custom cell height dynamically.
In my tableView there are three custom columns :
Product Name
Product Qty
Product Price
Now Product Name column does not show full name. Its shows only whatever text fits. But not able to show full name.
Though I've set heightForRowAtIndexPath as follows :
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
This is below lines are set in my CustomCell class
self.productNameLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.productNameLabel.numberOfLines = 0;
Example:
NSString *prodName = #"This is my product name which may contain description with it";
So in above example, my UITableViewCell shows only this much product name: This is my product and it escapes further string.
See below attached property section and constraints...
I want to set product name in Nutrient column
Please help me out, tried every possible solution but not able to resolve this.
This way my uitableview data is showing...but not able to show full name in nutrient.

First of all, estimatedRowHeight should be set to some specific number. It's an estimation that the tableView uses to calculate scroll indicator size, but does not really use it to size cells. So ideally you shoudl set something like:
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
Then make sure that your cells have defined autolayout constraints that can be used to define their size, specifically height. In your case, make sure that the label top anchor is constrained to the cell's contentView.topAnchor, and bottom anchor is constrained to the cell's contentView.bottomAnchor, and leading and trailing of label to leading and trailing of contentView. Thus, when the label has more of the content, it will try to expand, and so should the cell.
Consider using UIStackView to manage cell's content, because then even expanding and collapsing of the cells will easier to manage. If that's your goal, see my answer to another question.

Set these values for your label in Size Inspector(as shown in image) & don't set the height constraint for your label.

You have done good but need some more. First you set.
tableView.estimatedRowHeight = 44
After that bottom constraints which you are given equal to 13 just make it greater than or equal to 13. It will be works for you.

To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout. I just tested following steps and code and works fine.
Assign and implement tableview dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
Objective C:
// in ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property IBOutlet UITableView * table;
#end
// in ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.table.dataSource = self;
self.table.delegate = self;
self.table.rowHeight = UITableViewAutomaticDimension;
self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Swift:
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
For label instance in UITableviewCell
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.
Note: If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.

Related

UI Tableview cell not expanding when text is added at the runtime

I am using table view for my chat application, in which the chat responses are added either one of two textviews as shown in the image. The thing is whenever I add a large text, the cell height does not expand to show the full text. It only shows the last line of that text. I am using objective-c. Is there a way to solve this ?. I do not want to completely create a new view like the questions asked before, I want changes to this specific view.
First, you need to use Label and set top and bottom constraint between label and contentView cell.
In order to allow the self-sizing cell mechanism to work, you must set the rowHeight property on the table view to the constant UITableViewAutomaticDimension. Then, you simply need to enable row height estimation by setting the table view's estimatedRowHeight property to a nonzero value, for example:
_tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
_tableView.rowHeight = UITableViewAutomaticDimension;
You need to defined self-sizing cell like this.
tableCountries.estimatedRowHeight = 100.0;
tableCountries.rowHeight = UITableViewAutomaticDimension;
Make sure you have a proper constraint. You can have a constraint like this.
In the below label you need to set your textView top position. That's why when the below label expands it automatically expand label and text view will push to bottom.
I think your cell is not gettings its height because of textview scrolling maybe that will help:-
Use UITableViewAutomaticDimension and for that your tableview cell content should justify both top-bottom leading and trailing constraints to calculate cell height.
Set scrollEnabled to "false" for your textview.
With everything on the place your cell will get its height for sure.
you can do it in many ways :
1) You can set dynamic height of cell using UITableViewAutomaticDimension.
You need to set proper constraints in storyboard like setting top and bottom.
in ViewDidLoad add below :
tblView.rowHeight = UITableViewAutomaticDimension
tblView.estimatedRowHeight = 100
and in tableView cell's height method :
return UITableViewAutomaticDimension
2) Find text's height dynamically using below function :
let width = 100.0 \\your text label's width you gave
var height = chatMsg.text?.height(withConstrainedWidth: width, font: chatMsg.font!)
using above code you can find the height of your text. and you can use it dynamically
3) there are many code available to use for chat application in which you just need to pass text/image, and it will handle everything else.
Link : https://github.com/kerry/iMessageBubble
Add these two delegate methods:
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;
}
Create Custom UITableViewCell with Three UILabel like lbl1,lbl2,lbl3.
Add the AutoLayout Constraints to lbl1 left, right, top and bottom. With fixed height (30).
Then set same constraints to remaining two Labels.
Here is main things:
1.select lbl1 heigh and double click on height constraint and set >=30
Do same things with lbl2 and lbl3.
Final step is:
Select lbl2 top constraints, change the constraints property like <=8.
Do same things with lbl3, that's it we are done all.
thanks..

How to create dynamic tableview cell with dynamic tableview height in iOS

I want to increase tableview cell and tableview height based on content.
Suppose tableview contain 2 record and his 1st cell height is 100 and 2nd cell height is 25 then tableview height should be 100+25=125.
How to achieve this functionality?
Thanks in advance.
You can definitely do that,
First make sure your constraint of cells subView must set to top to bottom in order to calculate the height required for the cell.
Make sure your delegated are set as below
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 44;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Set height constraint of your tableView and make outlet of that constraint.
Add below method to your class where you want to resize your tableView dynamically.
- (void)adjustHeightOfTableview
{
CGFloat height = self.tableView.contentSize.height;
//CGFloat maxHeight = self.tableView.superview.frame.size.height - self.tableView.frame.origin.y;
/*
Here you have to take care of two things, if there is only tableView on the screen then you have to see is your tableView going below screen using maxHeight and your screen height,
Or you can add your tableView inside scrollView so that your tableView can increase its height as much it requires based on the number of cell (with different height based on content) it has to display.
*/
// now set the height constraint accordingly
self.constraintHeightTableView.constant = height;
//If you want to increase tableView height with animation you can do that as below.
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
}
Call this method when you are ready with the dataSource for the table, and call the method as
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
//In my case i had to call this method after some delay, because (i think) it will allow tableView to reload completely and then calculate the height required for itself. (This might be a workaround, but it worked for me)
[self performSelector:#selector(adjustHeightOfTableview) withObject:nil afterDelay:0.3];
});
If you are running iOS 8+,
You can use:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80 // your desired or expected height
properties.
for this to take effect you should not have any height set in heightForRowAtIndexpath
You should set the cell constraints i.e., constraints for the elements present inside cell, so the set constraints are enough for the tableviewcell to calculate it's height in run time
Solution in swift 3
Step 1.
Set the top, bottom, leading and trailing constraints (do not make it constant height, but do set a constant height constraint as of now).
Now we gonna change this height dynamically based on the number of cells and its height.
Step 2.
Drag an outlet of that height constraint to the class, and copy this function anywhere in the class.
func adjustHeightOfTableview() {
let height: CGFloat = tableview.contentSize.height
self.outLetOfTheHeightConstraint.constant = height
}
Step 3.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
DispatchQueue.main.async {
self.tableview.reloadData()
self.perform(#selector(self.adjustHeightOfTableview))
}
}
self.tableView.frame = CGRectMake(self.tableView.origin.x, self.tableView.origin.y, self.tableView.frame.size.width, 125);

UITableView with customized and resized UITableViewCell [duplicate]

How do you use Auto Layout within UITableViewCells in a table view to let each cell's content and subviews determine the row height (itself/automatically), while maintaining smooth scrolling performance?
TL;DR: Don't like reading? Jump straight to the sample projects on GitHub:
iOS 8 Sample Project - Requires iOS 8
iOS 7 Sample Project - Works on iOS 7+
Conceptual Description
The first 2 steps below are applicable regardless of which iOS versions you are developing for.
1. Set Up & Add Constraints
In your UITableViewCell subclass, add constraints so that the subviews of the cell have their edges pinned to the edges of the cell's contentView (most importantly to the top AND bottom edges). NOTE: don't pin subviews to the cell itself; only to the cell's contentView! Let the intrinsic content size of these subviews drive the height of the table view cell's content view by making sure the content compression resistance and content hugging constraints in the vertical dimension for each subview are not being overridden by higher-priority constraints you have added. (Huh? Click here.)
Remember, the idea is to have the cell's subviews connected vertically to the cell's content view so that they can "exert pressure" and make the content view expand to fit them. Using an example cell with a few subviews, here is a visual illustration of what some (not all!) of your constraints would need to look like:
You can imagine that as more text is added to the multi-line body label in the example cell above, it will need to grow vertically to fit the text, which will effectively force the cell to grow in height. (Of course, you need to get the constraints right in order for this to work correctly!)
Getting your constraints right is definitely the hardest and most important part of getting dynamic cell heights working with Auto Layout. If you make a mistake here, it could prevent everything else from working -- so take your time! I recommend setting up your constraints in code because you know exactly which constraints are being added where, and it's a lot easier to debug when things go wrong. Adding constraints in code can be just as easy as and significantly more powerful than Interface Builder using layout anchors, or one of the fantastic open source APIs available on GitHub.
If you're adding constraints in code, you should do this once from within the updateConstraints method of your UITableViewCell subclass. Note that updateConstraints may be called more than once, so to avoid adding the same constraints more than once, make sure to wrap your constraint-adding code within updateConstraints in a check for a boolean property such as didSetupConstraints (which you set to YES after you run your constraint-adding code once). On the other hand, if you have code that updates existing constraints (such as adjusting the constant property on some constraints), place this in updateConstraints but outside of the check for didSetupConstraints so it can run every time the method is called.
2. Determine Unique Table View Cell Reuse Identifiers
For every unique set of constraints in the cell, use a unique cell reuse identifier. In other words, if your cells have more than one unique layout, each unique layout should receive its own reuse identifier. (A good hint that you need to use a new reuse identifier is when your cell variant has a different number of subviews, or the subviews are arranged in a distinct fashion.)
For example, if you were displaying an email message in each cell, you might have 4 unique layouts: messages with just a subject, messages with a subject and a body, messages with a subject and a photo attachment, and messages with a subject, body, and photo attachment. Each layout has completely different constraints required to achieve it, so once the cell is initialized and the constraints are added for one of these cell types, the cell should get a unique reuse identifier specific to that cell type. This means when you dequeue a cell for reuse, the constraints have already been added and are ready to go for that cell type.
Note that due to differences in intrinsic content size, cells with the same constraints (type) may still have varying heights! Don't confuse fundamentally different layouts (different constraints) with different calculated view frames (solved from identical constraints) due to different sizes of content.
Do not add cells with completely different sets of constraints to the same reuse pool (i.e. use the same reuse identifier) and then attempt to remove the old constraints and set up new constraints from scratch after each dequeue. The internal Auto Layout engine is not designed to handle large scale changes in constraints, and you will see massive performance issues.
For iOS 8 - Self-Sizing Cells
3. Enable Row Height Estimation
To enable self-sizing table view cells, you must set the table view’s
rowHeight property to UITableViewAutomaticDimension. You must also
assign a value to the estimatedRowHeight property. As soon as both of
these properties are set, the system uses Auto Layout to calculate the
row’s actual height
Apple: Working with Self-Sizing Table View Cells
With iOS 8, Apple has internalized much of the work that previously had to be implemented by you prior to iOS 8. In order to allow the self-sizing cell mechanism to work, you must first set the rowHeight property on the table view to the constant UITableView.automaticDimension. Then, you simply need to enable row height estimation by setting the table view's estimatedRowHeight property to a nonzero value, for example:
self.tableView.rowHeight = UITableView.automaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
What this does is provide the table view with a temporary estimate/placeholder for the row heights of cells that are not yet onscreen. Then, when these cells are about to scroll on screen, the actual row height will be calculated. To determine the actual height for each row, the table view automatically asks each cell what height its contentView needs to be based on the known fixed width of the content view (which is based on the table view's width, minus any additional things like a section index or accessory view) and the auto layout constraints you have added to the cell's content view and subviews. Once this actual cell height has been determined, the old estimated height for the row is updated with the new actual height (and any adjustments to the table view's contentSize/contentOffset are made as needed for you).
Generally speaking, the estimate you provide doesn't have to be very accurate -- it is only used to correctly size the scroll indicator in the table view, and the table view does a good job of adjusting the scroll indicator for incorrect estimates as you scroll cells onscreen. You should set the estimatedRowHeight property on the table view (in viewDidLoad or similar) to a constant value that is the "average" row height. Only if your row heights have extreme variability (e.g. differ by an order of magnitude) and you notice the scroll indicator "jumping" as you scroll should you bother implementing tableView:estimatedHeightForRowAtIndexPath: to do the minimal calculation required to return a more accurate estimate for each row.
For iOS 7 support (implementing auto cell sizing yourself)
3. Do a Layout Pass & Get The Cell Height
First, instantiate an offscreen instance of a table view cell, one instance for each reuse identifier, that is used strictly for height calculations. (Offscreen meaning the cell reference is stored in a property/ivar on the view controller and never returned from tableView:cellForRowAtIndexPath: for the table view to actually render onscreen.) Next, the cell must be configured with the exact content (e.g. text, images, etc) that it would hold if it were to be displayed in the table view.
Then, force the cell to immediately layout its subviews, and then use the systemLayoutSizeFittingSize: method on the UITableViewCell's contentView to find out what the required height of the cell is. Use UILayoutFittingCompressedSize to get the smallest size required to fit all the contents of the cell. The height can then be returned from the tableView:heightForRowAtIndexPath: delegate method.
4. Use Estimated Row Heights
If your table view has more than a couple dozen rows in it, you will find that doing the Auto Layout constraint solving can quickly bog down the main thread when first loading the table view, as tableView:heightForRowAtIndexPath: is called on each and every row upon first load (in order to calculate the size of the scroll indicator).
As of iOS 7, you can (and absolutely should) use the estimatedRowHeight property on the table view. What this does is provide the table view with a temporary estimate/placeholder for the row heights of cells that are not yet onscreen. Then, when these cells are about to scroll on screen, the actual row height will be calculated (by calling tableView:heightForRowAtIndexPath:), and the estimated height updated with the actual one.
Generally speaking, the estimate you provide doesn't have to be very accurate -- it is only used to correctly size the scroll indicator in the table view, and the table view does a good job of adjusting the scroll indicator for incorrect estimates as you scroll cells onscreen. You should set the estimatedRowHeight property on the table view (in viewDidLoad or similar) to a constant value that is the "average" row height. Only if your row heights have extreme variability (e.g. differ by an order of magnitude) and you notice the scroll indicator "jumping" as you scroll should you bother implementing tableView:estimatedHeightForRowAtIndexPath: to do the minimal calculation required to return a more accurate estimate for each row.
5. (If Needed) Add Row Height Caching
If you've done all the above and are still finding that performance is unacceptably slow when doing the constraint solving in tableView:heightForRowAtIndexPath:, you'll unfortunately need to implement some caching for cell heights. (This is the approach suggested by Apple's engineers.) The general idea is to let the Autolayout engine solve the constraints the first time, then cache the calculated height for that cell and use the cached value for all future requests for that cell's height. The trick of course is to make sure you clear the cached height for a cell when anything happens that could cause the cell's height to change -- primarily, this would be when that cell's content changes or when other important events occur (like the user adjusting the Dynamic Type text size slider).
iOS 7 Generic Sample Code (with lots of juicy comments)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path, depending on the particular layout required (you may have
// just one, or may have many).
NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.
// Note that this method will init and return a new cell if there isn't
// one available in the reuse pool, so either way after this line of
// code you will have a cell with the correct constraints ready to go.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the
// preferredMaxLayoutWidth needs to be set correctly. Do it at this
// point if you are NOT doing it within the UITableViewCell subclass
// -[layoutSubviews] method. For example:
// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path.
NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse
// identifier, creating a cell and storing it in the dictionary if one
// hasn't already been added for the reuse identifier. WARNING: Don't
// call the table view's dequeueReusableCellWithIdentifier: method here
// because this will result in a memory leak as the cell is created but
// never returned from the tableView:cellForRowAtIndexPath: method!
UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[YourTableViewCellClass alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Set the width of the cell to match the width of the table view. This
// is important so that we'll get the correct cell height for different
// table view widths if the cell's height depends on its width (due to
// multi-line UILabels word wrapping, etc). We don't need to do this
// above in -[tableView:cellForRowAtIndexPath] because it happens
// automatically when the cell is used in the table view. Also note,
// the final width of the cell may not be the width of the table view in
// some cases, for example when a section index is displayed along
// the right side of the table view. You must account for the reduced
// cell width.
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Do the layout pass on the cell, which will calculate the frames for
// all the views based on the constraints. (Note that you must set the
// preferredMaxLayoutWidth on multiline UILabels inside the
// -[layoutSubviews] method of the UITableViewCell subclass, or do it
// manually at this point before the below 2 lines!)
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell's contentView
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Add an extra point to the height to account for the cell separator,
// which is added between the bottom of the cell's contentView and the
// bottom of the table view cell.
height += 1.0;
return height;
}
// NOTE: Set the table view's estimatedRowHeight property instead of
// implementing the below method, UNLESS you have extreme variability in
// your row heights and you notice the scroll indicator "jumping"
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do the minimal calculations required to be able to return an
// estimated row height that's within an order of magnitude of the
// actual height. For example:
if ([self isTallCellAtIndexPath:indexPath]) {
return 350.0;
} else {
return 40.0;
}
}
Sample Projects
iOS 8 Sample Project - Requires iOS 8
iOS 7 Sample Project - Works on iOS 7+
These projects are fully working examples of table views with variable row heights due to table view cells containing dynamic content in UILabels.
Xamarin (C#/.NET)
If you're using Xamarin, check out this sample project put together by #KentBoogaart.
For iOS 8 above it's really simple:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 80
self.tableView.rowHeight = UITableView.automaticDimension
}
or
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableView.automaticDimension
}
But for iOS 7, the key is calculate the height after autolayout:
func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
cell.setNeedsLayout()
cell.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
return height
}
Important
If multiple lines labels, don't forget set the numberOfLines to 0.
Don't forget label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)
The full example code is here.
Swift example of a variable height UITableViewCell
Updated for Swift 3
William Hu's Swift answer is good, but it helps me to have some simple yet detailed steps when learning to do something for the first time. The example below is my test project while learning to make a UITableView with variable cell heights. I based it on this basic UITableView example for Swift.
The finished project should look like this:
Create a new project
It can be just a Single View Application.
Add the code
Add a new Swift file to your project. Name it MyCustomCell. This class will hold the outlets for the views that you add to your cell in the storyboard. In this basic example we will only have one label in each cell.
import UIKit
class MyCustomCell: UITableViewCell {
#IBOutlet weak var myCellLabel: UILabel!
}
We will connect this outlet later.
Open ViewController.swift and make sure you have the following content:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// These strings will be the data for the table view cells
let animals: [String] = [
"Ten horses: horse horse horse horse horse horse horse horse horse horse ",
"Three cows: cow, cow, cow",
"One camel: camel",
"Ninety-nine sheep: sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
"Thirty goats: goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]
// Don't forget to enter this in IB also
let cellReuseIdentifier = "cell"
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// delegate and data source
tableView.delegate = self
tableView.dataSource = self
// Along with auto layout, these are the keys for enabling variable cell height
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.animals[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
Important Note:
It is the following two lines of code (along with auto layout) that make the variable cell height possible:
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
Setup the storyboard
Add a Table View to your view controller and use auto layout to pin it to the four sides. Then drag a Table View Cell onto the Table View. And onto the Prototype cell, drag a Label. Use auto layout to pin the label to the four edges of the content view of the Table View Cell.
Important note:
Auto layout works together with the important two lines of code I mentioned above. If you don't use auto layout it isn't going to work.
Other IB settings
Custom class name and Identifier
Select the Table View Cell and set the custom class to be MyCustomCell (the name of the class in the Swift file we added). Also set the Identifier to be cell (the same string that we used for the cellReuseIdentifier in the code above.
Zero Lines for Label
Set the number of lines to 0 in your Label. This means multi-line and allows the label to resize itself based on its content.
Hook Up the Outlets
Control drag from the Table View in the storyboard to the tableView variable in the ViewController code.
Do the same for the Label in your Prototype cell to the myCellLabel variable in the MyCustomCell class.
Finished
You should be able to run your project now and get cells with variable heights.
Notes
This example only works for iOS 8 and after. If you are still needing to support iOS 7 then this won't work for you.
Your own custom cells in your future projects will probably have more than a single label. Make sure that you get everything pinned right so that auto layout can determine the correct height to use. You may also have to use vertical compression resistance and hugging. See this article for more about that.
If you are not pinning the leading and trailing (left and right) edges, you may also need to set the label's preferredMaxLayoutWidth so that it knows when to line wrap. For example, if you had added a Center Horizontally constraint to the label in the project above rather than pin the leading and trailing edges, then you would need to add this line to the tableView:cellForRowAtIndexPath method:
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
See also
Understanding Self Sizing Cells and Dynamic Type in iOS 8
Table View Cells with Varying Row Heights
UITableView example for Swift
I wrapped #smileyborg's iOS7 solution in a category
I decided to wrap this clever solution by #smileyborg into a UICollectionViewCell+AutoLayoutDynamicHeightCalculation category.
The category also rectifies the issues outlined in #wildmonkey's answer (loading a cell from a nib and systemLayoutSizeFittingSize: returning CGRectZero)
It doesn't take into account any caching but suits my needs right now. Feel free to copy, paste and hack at it.
UICollectionViewCell+AutoLayoutDynamicHeightCalculation.h
#import <UIKit/UIKit.h>
typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);
/**
* A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
*
* Many thanks to #smileyborg and #wildmonkey
*
* #see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
*/
#interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)
/**
* Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
*
* #param name Name of the nib file.
*
* #return collection view cell for using to calculate content based height
*/
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;
/**
* Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
*
* #param block Render the model data to your UI elements in this block
*
* #return Calculated constraint derived height
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;
/**
* Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;
#end
UICollectionViewCell+AutoLayoutDynamicHeightCalculation.m
#import "UICollectionViewCell+AutoLayout.h"
#implementation UICollectionViewCell (AutoLayout)
#pragma mark Dummy Cell Generator
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
[heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
return heightCalculationCell;
}
#pragma mark Moving Constraints
- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
[self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
[self removeConstraint:constraint];
id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}];
}
#pragma mark Height
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
NSParameterAssert(block);
block();
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return calculatedSize.height;
}
#end
Usage example:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
[(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
}];
return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}
Thankfully we won't have to do this jazz in iOS8, but there it is for now!
Here is my solution:
You need to tell the TableView the estimatedHeight before it loads the view. Otherwise it wont be able to behave like expected.
Objective-C
- (void)viewWillAppear:(BOOL)animated {
_messageField.delegate = self;
_tableView.estimatedRowHeight = 65.0;
_tableView.rowHeight = UITableViewAutomaticDimension;
}
Update to Swift 4.2
override func viewWillAppear(_ animated: Bool) {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 65.0
}
The solution proposed by #smileyborg is almost perfect. If you have a custom cell and you want one or more UILabel with dynamic heights then the systemLayoutSizeFittingSize method combined with AutoLayout enabled returns a CGSizeZero unless you move all your cell constraints from the cell to its contentView (as suggested by #TomSwift here How to resize superview to fit all subviews with autolayout?).
To do so you need to insert the following code in your custom UITableViewCell implementation (thanks to #Adrian).
- (void)awakeFromNib{
[super awakeFromNib];
for (NSLayoutConstraint *cellConstraint in self.constraints) {
[self removeConstraint:cellConstraint];
id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
NSLayoutConstraint *contentViewConstraint =
[NSLayoutConstraint constraintWithItem:firstItem
attribute:cellConstraint.firstAttribute
relatedBy:cellConstraint.relation
toItem:seccondItem
attribute:cellConstraint.secondAttribute
multiplier:cellConstraint.multiplier
constant:cellConstraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
Mixing #smileyborg answer with this should works.
An important enough gotcha I just ran into to post as an answer.
#smileyborg's answer is mostly correct. However, if you have any code in the layoutSubviews method of your custom cell class, for instance setting the preferredMaxLayoutWidth, then it won't be run with this code:
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
It confounded me for awhile. Then I realized it's because those are only triggering layoutSubviews on the contentView, not the cell itself.
My working code looks like this:
TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
Note that if you are creating a new cell, I'm pretty sure you don't need to call setNeedsLayout as it should already be set. In cases where you save a reference to a cell, you should probably call it. Either way it shouldn't hurt anything.
Another tip if you are using cell subclasses where you are setting things like preferredMaxLayoutWidth. As #smileyborg mentions, "your table view cell hasn't yet had its width fixed to the table view's width". This is true, and trouble if you are doing your work in your subclass and not in the view controller. However you can simply set the cell frame at this point using the table width:
For instance in the calculation for height:
self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:#"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);
(I happen to cache this particular cell for re-use, but that's irrelevant).
As long as your layout in your cell is good.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}
Update: You should use dynamic resizing introduced in iOS 8.
(for Xcode 8.x / Xcode 9.x read at the bottom)
Beware of the following issue in in Xcode 7.x, which might be a source of confusion:
Interface Builder does not handle auto-sizing cell set-up properly. Even if your constraints are absolutely valid, IB will still complain and give you confusing suggestions and errors. The reason is that IB is unwilling to change the row's height as your constraints dictate (so that the cell fits around your content). Instead, it keeps the row's height fixed and starts suggesting you change your constraints, which you should ignore.
For example, imagine you've set up everything fine, no warnings, no errors, all works.
Now if you change the font size (in this example I'm changing the description label font size from 17.0 to 18.0).
Because the font size increased, the label now wants to occupy 3 rows (before that it was occupying 2 rows).
If Interface Builder worked as expected, it would resize the cell's height to accommodate the new label height. However what actually happens is that IB displays the red auto-layout error icon and suggest that you modify hugging/compression priorities.
You should ignore these warnings. What you can* do instead is to manually change the row's height in (select Cell > Size Inspector > Row Height).
I was changing this height one click at a time (using the up/down stepper) until the red arrow errors disappear! (you will actually get yellow warnings, at which point just go ahead and do 'update frames', it should all work).
* Note that you don't actually have to resolve these red errors or yellow warnings in Interface Builder - at runtime, everything will work correctly (even if IB shows errors/warnings). Just make sure that at runtime in the console log you're not getting any AutoLayout errors.
In fact trying to always update row height in IB is super annoying and sometimes close to impossible (because of fractional values).
To prevent the annoying IB warnings/errors, you can select the views involved and in Size Inspector for the property Ambiguity choose Verify Position Only
Xcode 8.x / Xcode 9.x seems to (sometimes) be doing things differently than Xcode 7.x, but still incorrectly. For example even when compression resistance priority / hugging priority are set to required (1000), Interface Builder might stretch or clip a label to fit the cell (instead of resizing cell height to fit around the label). And in such a case it might not even show any AutoLayout warnings or errors. Or sometimes it does exactly what Xcode 7.x did, described above.
In case people are still having trouble with this. I wrote a quick blog post about using Autolayout with UITableViews Leveraging Autolayout For Dynamic Cell Heights as well as an open source component to help make this more abstract and easier to implement.
https://github.com/Raizlabs/RZCellSizeManager
To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement tableview dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
Objective C:
// in ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property IBOutlet UITableView * table;
#end
// in ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.table.dataSource = self;
self.table.delegate = self;
self.table.rowHeight = UITableViewAutomaticDimension;
self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Swift:
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// 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 label instance in UITableviewCell
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.
Note: If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.
Like #Bob-Spryn I ran into an important enough gotcha that I'm posting this as an answer.
I struggled with #smileyborg's answer for a while. The gotcha that I ran into is if you've defined your prototype cell in IB with additional elements (UILabels, UIButtons, etc.) in IB when you instantiate the cell with [[YourTableViewCellClass alloc] init] it will not instantiate all the other elements within that cell unless you've written code to do that. (I had a similar experience with initWithStyle.)
To have the storyboard instantiate all the additional elements obtain your cell with [tableView dequeueReusableCellWithIdentifier:#"DoseNeeded"] (Not [tableView dequeueReusableCellWithIdentifier:forIndexPath:] as this'll cause interesting problems.) When you do this all the elements you defined in IB will be instantiated.
Dynamic Table View Cell Height and Auto Layout
A good way to solve the problem with storyboard Auto Layout:
- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
static RWImageCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];
});
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
Another "solution": skip all this frustration and use a UIScrollView instead to get a result that looks and feels identical to UITableView.
That was the painful "solution" for me, after having put in literally 20+ very frustrating hours total trying to build something like what smileyborg suggested and failing over many months and three versions of App Store releases.
My take is that if you really need iOS 7 support (for us, it's essential) then the technology is just too brittle and you'll pull your hair out trying. And that UITableView is complete overkill generally unless you're using some of the advanced row editing features and/or really need to support 1000+ "rows" (in our app, it's realistically never more than 20 rows).
The added bonus is that the code gets insanely simple versus all the delegate crap and back and forth that comes with UITableView. It's just one single loop of code in viewOnLoad that looks elegant and is easy to manage.
Here are some tips on how to do it:
Using either Storyboard or a nib file, create a ViewController and associated root view.
Drag over a UIScrollView onto your root view.
Add constraints top, bottom, left, and right constraints to the top-level view so the UIScrollView fills the entire root view.
Add a UIView inside the UIScrollView and call it "container". Add top, bottom, left and right constraints to the UIScrollView (its parent). KEY TRICK: Also add an "Equal widths" constraint to link the UIScrollView and UIView.
NOTE: You will get an error "scroll view has ambiguous scrollable content height" and that your container UIView should have a height of 0 pixels. Neither error seems to matter when the app is running.
Create nib files and controllers for each of your "cells". Use UIView not UITableViewCell.
In your root ViewController, you essentially add all the "rows" to the container UIView and programmatically add constraints linking their left and right edges to the container view, their top edges to either the container view top (for the first item) or the previous cell. Then link the final cell to the container bottom.
For us, each "row" is in a nib file. So the code looks something like this:
class YourRootViewController {
#IBOutlet var container: UIView! //container mentioned in step 4
override func viewDidLoad() {
super.viewDidLoad()
var lastView: UIView?
for data in yourDataSource {
var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
UITools.addViewToTop(container, child: cell.view, sibling: lastView)
lastView = cell.view
//Insert code here to populate your cell
}
if(lastView != nil) {
container.addConstraint(NSLayoutConstraint(
item: lastView!,
attribute: NSLayoutAttribute.Bottom,
relatedBy: NSLayoutRelation.Equal,
toItem: container,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 0))
}
///Add a refresh control, if you want - it seems to work fine in our app:
var refreshControl = UIRefreshControl()
container.addSubview(refreshControl!)
}
}
And here's the code for UITools.addViewToTop:
class UITools {
///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
{
child.setTranslatesAutoresizingMaskIntoConstraints(false)
container.addSubview(child)
//Set left and right constraints so fills full horz width:
container.addConstraint(NSLayoutConstraint(
item: child,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: container,
attribute: NSLayoutAttribute.Left,
multiplier: 1,
constant: 0))
container.addConstraint(NSLayoutConstraint(
item: child,
attribute: NSLayoutAttribute.Trailing,
relatedBy: NSLayoutRelation.Equal,
toItem: container,
attribute: NSLayoutAttribute.Right,
multiplier: 1,
constant: 0))
//Set vertical position from last item (or for first, from the superview):
container.addConstraint(NSLayoutConstraint(
item: child,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: sibling == nil ? container : sibling,
attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 0))
}
}
The only "gotcha" I've found with this approach so far is that UITableView has a nice feature of "floating" section headers at the top of the view as you scroll. The above solution won't do that unless you add more programming but for our particular case this feature wasn't 100% essential and nobody noticed when it went away.
If you want dividers between your cells, just add a 1 pixel high UIView at the bottom of your custom "cell" that looks like a divider.
Be sure to turn on "bounces" and "bounce vertically" for the refresh control to work and so it seems more like a tableview.
TableView shows some empty rows and dividers under your content, if it doesn't fill the full screen where as this solution doesn't. But personally, I prefer if those empty rows weren't there anyway - with variable cell height it always looked "buggy" to me anyway to have the empty rows in there.
Here's hoping some other programmer reads my post BEFORE wasting 20+ hours trying to figure it out with Table View in their own app. :)
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension
I had to use dynamic views (setup views and constraints by code) and when I wanted to set preferredMaxLayoutWidth label's width was 0. So I've got wrong cell height.
Then I added
[cell layoutSubviews];
before executing
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
After that label's width was as expected and dynamic height was calculating right.
Let's say you have a cell with a subview, and you want the cell's height to be high enough to encompass the subview + padding.
1) Set the subview's bottom constraint equal to the cell.contentView minus the padding you want. Do not set constraints on the cell or cell.contentView itself.
2) Set either the tableView's rowHeight property or tableView:heightForRowAtIndexPath: to UITableViewAutomaticDimension.
3) Set either the tableView's estimatedRowHeight property or tableView:estimatedHeightForRowAtIndexPath: to a best guess of the height.
That's it.
If you do you layout programmatically, here is what to consider for iOS 10 using anchors in Swift.
There are three rules/ steps
NUMBER 1: set this two properties of tableview on viewDidLoad, the first one is telling to the tableview that should expect dynamic sizes on their cells, the second one is just to let the app calculate the size of the scrollbar indicator, so it helps for performance.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
NUMBER 2: This is important you need to add the subviews to the contentView of the cell not to the view, and also use its layoutsmarginguide to anchor the subviews to the top and bottom, this is a working example of how to do it.
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
}
private func setUpViews() {
contentView.addSubview(movieImageView)
contentView.addSubview(descriptionLabel)
let marginGuide = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
movieImageView.heightAnchor.constraint(equalToConstant: 80),
movieImageView.widthAnchor.constraint(equalToConstant: 80),
movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),
descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)
])
}
Create a method that will add the subviews and perform the layout, call it in the init method.
NUMBER 3: DON'T CALL THE METHOD:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
}
If you do it you will override your implementation.
Follow this 3 rules for dynamic cells in tableviews.
here is a working implementation
https://github.com/jamesrochabrun/MinimalViewController
If you have a long string. e.g. one which doesn't have a line break. Then you you might run into some problems.
The "alleged" fix is mentioned by the accepted answer and few other answers. You just need to add
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
I find Suragh's answer the most complete and concise, hence not confusing.
Though non explain why these changes are needed. Let's do that.
Drop the following code in to a project.
import UIKit
class ViewController: UIViewController {
lazy var label : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.backgroundColor = .red
lbl.textColor = .black
return lbl
}()
override func viewDidLoad() {
super.viewDidLoad()
// step0: (0.0, 0.0)
print("empty Text intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step1: (29.0, 20.5)
label.text = "hiiiii"
print("hiiiii intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step2: (328.0, 20.5)
label.text = "translatesAutoresizingMaskIntoConstraints"
print("1 translate intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step3: (992.0, 20.5)
label.text = "translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints"
print("3 translate intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step4: (328.0, 20.5)
label.text = "translatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints"
print("3 translate w/ line breaks (but the line breaks get ignored, because numberOfLines is defaulted to `1` and it will force it all to fit into one line! intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step5: (328.0, 61.0)
label.numberOfLines = 0
print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step6: (98.5, 243.5)
label.preferredMaxLayoutWidth = 100
print("3 translate w/ line breaks | '0' numberOfLines | preferredMaxLayoutWidth: 100 intrinsicContentSize: \(label.intrinsicContentSize)")
setupLayout()
}
func setupLayout(){
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
Note that I haven't added any size constraints. I've only added centerX, centerY constraints. But still the label will be sized correctly Why?
Because of contentSize.
To better process this, first keep step0, then comment out out steps 1-6. Let setupLayout() stay. Observe the behavior.
Then uncomment step1, and observe.
Then uncomment step2 and observe.
Do this until you've uncommented all 6 steps and observed their behaviors.
What can conclude from all this? What factors can change the contenSize?
Text Length: If you have a longer text then your intrinsicContentSize's width will increase
Line breaks: If you add \n then the intrinsicContentSize's width will the maximum width of all lines. If one line has 25 characters, another has 2 characters and another has 21 characters then your width will be calculated based the 25 characters
Number of allowed lines: You must set the numberOfLines to 0 otherwise the you won't have multiple lines. Your numberOfLines will adjust your intrinsicContentSize's height
Making adjustments: Imagine that based on your text, your intrinsicContentSize's width was 200 and height was 100, but you wanted to limited the width to the label's container what are you going to do? The solution is to set it to a desired width. You do that by setting preferredMaxLayoutWidth to 130 then your new intrinsicContentSize will have a width of roughly 130. The height would obviously be more than 100 because you'd need more lines.
That being said if your constraints are set correctly then you won't need to use this at all! For more on that see this answer and its comments. You only need to use preferredMaxLayoutWidth if you don't have constraints restricting the width/height as in one might say "don't wrap the text unless it exceeds the preferredMaxLayoutWidth". But with 100% certainty if you set the leading/trailing and numberOfLines to 0 then you're good! Long story short most answers here which recommend using it are WRONG! You don't need it. Needing it is a sign that your constraints are not set correctly or that you just don't have constraints
Font Size: Also note that if you increase your fontSize then the intrinsicContentSize's height will increase. I didn't show that in my code. You can try that on your own.
So back to your tableViewCell example:
All you need to do is:
set the numberOfLines to 0
constrain the label correctly to the margins/edges
There is no need to set preferredMaxLayoutWidth.
In my case i have to create a custom cell with a image which is coming from server and can be of any width and height. And two UILabels with dynamic size(both width & height)
i have achieved the same here in my answer with autolayout and programmatically:
Basically above #smileyBorg answer helped but systemLayoutSizeFittingSize never worked for me, In my approach :
1. No use of automatic row height calculation property. 2.No use of estimated height 3.No need of unnecessary updateConstraints. 4.No use of Automatic Preferred Max Layout Width. 5. No use of systemLayoutSizeFittingSize (should have use but not working for me, i dont know what it is doing internally), but instead my method -(float)getViewHeight working and i know what it's doing internally.
Is it possible to have differing heights in a UITableView Cell when I use several different ways of displaying the cell?
In my case, the padding was because of the sectionHeader and sectionFooter heights, where storyboard allowed me to change it to minimum 1. So in viewDidLoad method:
tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
I just did some dumb try and error with the 2 values of rowHeight and estimatedRowHeight and just thought it might provide some debugging insight:
If you set them both OR only set the estimatedRowHeight you will get the desired behavior:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1
It's suggested that you do your best to get the correct estimate, but the end result isn't different. It will just affect your performance.
If you only set the rowHeight ie only do:
tableView.rowHeight = UITableViewAutomaticDimension
your end result would not be as desired:
If you set the estimatedRowHeight to 1 or smaller then you will crash regardless of the rowHeight.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1
I crashed with the following error message:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
...some other lines...
libc++abi.dylib: terminating with uncaught exception of type
NSException
With regard to the accepted answer by #smileyborg, I have found
[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]
to be unreliable in some cases where constraints are ambiguous. Better to force the layout engine to calculate the height in one direction, by using the helper category on UIView below:
-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
CGFloat h = size.height;
return h;
}
Where w: is the width of the tableview
Simply add these two functions in your viewcontroller it will solve your problem. Here, list is a string array which contain your string of every row.
func tableView(_ tableView: UITableView,
estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])
return (tableView.rowHeight)
}
func calculateHeight(inString:String) -> CGFloat
{
let messageString = input.text
let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]
let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)
let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)
let requredSize:CGRect = rect
return requredSize.height
}
swift 4
#IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var tableView: UITableView!
private var context = 1
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
}
// Added observer to adjust tableview height based on the content
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.context{
if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize{
print("-----")
print(size.height)
tableViewHeightConstraint.constant = size.height + 50
}
}
}
//Remove observer
deinit {
NotificationCenter.default.removeObserver(self)
}
If the cell height is dynamic by the content, you should precisely count it out and then return the height value before the cell is rendered. An easy way is to define the counting method in the table view cell code for controller to call at the table cell height delegate method. Don't forget to count out the real cell frame width (default is 320) if the height is rely on the width of the table or screen. That is, in the table cell height delegate method, use cell.frame to correct the cell width first, then call the counting height method defined in the cell to get the suitable value and return it.
PS. The code for generating cell object could be defined in another method for different table view cell delegate method to call.
UITableView.automaticDimension can be set via Interface Builder:
Xcode > Storyboard > Size Inspector
Table View Cell > Row Height > Automatic
yet another iOs7+iOs8 solution in Swift
var cell2height:CGFloat=44
override func viewDidLoad() {
super.viewDidLoad()
theTable.rowHeight = UITableViewAutomaticDimension
theTable.estimatedRowHeight = 44.0;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
cell2height=cell.contentView.height
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if #available(iOS 8.0, *) {
return UITableViewAutomaticDimension
} else {
return cell2height
}
}

why UITableViewAutomaticDimension not working?

Hi there is plenty of question answering the dynamic height for UITableViewCell of UITableView. However I find it weird when I did it.
here's are some of the answer :
here and here
usually this would answer the dynamic height for cell
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableView.automaticDimension
but in my case I wonder this line wont do anything.
my UITableView is being viewed after clicking tabbar inside the splitview. Is this helpful?
Maybe I'm Missing something. Could anyone help me I spent 2 hours doing silly.
These are my constraint for title the title could be long but the label is not.
and this is my cell
In order to make UITableViewAutomaticDimension work you have to set all left, right, bottom, and top constraints relative to cell container view. In your case you will need to add the missing bottom space to superview constraint for label under the title
I had added constraints programmatically, and accidentally added them to the cell directly, i.e. not on the contentView property. Adding the constraints to contentView resolved it for me!
To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement tableview dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
Objective C:
// in ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property IBOutlet UITableView * table;
#end
// in ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.table.dataSource = self;
self.table.delegate = self;
self.table.rowHeight = UITableViewAutomaticDimension;
self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Swift:
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// 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 label instance in UITableviewCell
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.
Note: If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.
Also make sure the Lines for your UILabel in the cell is set to 0. If it is set to 1, it will not grow vertically.
I have a specific case, and 99% of the solutions I read didn't work. I thought I'd share my experience. Hope it can helps, as I'd struggled for a couple of hours before fixing this issue.
My scenario:
I'm using two TableViews in one ViewController
I am loading Custom Cells (xib) in those tableviews
Dynamic content in cells consists of two labels
Page uses a ScrollView
What I needed to achieve:
Dynamic TableView height
Dynamic TableViewCells height
What you'll need to check to make it work smoothly:
Like every tutorial and answer will tell you, set all the constraints for your label/content (top, bottom, leading & trailing), and set it to the ContentView (Superview). This video tutorial will be helpful.
In the viewWillAppear method of my ViewController, I am giving one of my tableViews (tableView2) an estimated row height and then use the UITableViewAutomaticDimension as such (and as seen in all tutorials/answers):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView2.estimatedRowHeight = 80
tableView2.rowHeight = UITableViewAutomaticDimension
}
For me, these previous steps were not enough. My TableView would simply take the estimatedRowHeight and multiply it by the number of cells. Meeeh.
As I am using two different tableViews, I created an outlet for each of them, tableView1 and tableView2. Allowing me to resize them in the viewDidLayoutSubviews method. Feel free to ask in comment how I did this.
One additional step fixed it for me, by adding this to the viewDidAppear method:
override func viewDidAppear(_ animated: Bool) {
tableView1.reloadData() // reloading data for the first tableView serves another purpose, not exactly related to this question.
tableView2.setNeedsLayout()
tableView2.layoutIfNeeded()
tableView2.reloadData()
}
Hope that helps someone!
As of iOS 11 and Xcode 9.3, most of the above information is wrong. Apple's Guide suggests setting
tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableViewAutomaticDimension
WWDC 2017 session 245 (Building Apps with Dynamic Type, about 16:33 into the video) suggests something similar. None of this made a difference for me.
For a Subtitle style cell, all that's necessary to get self-sizing table view cells is to set the number of lines (in the Label section of the attributes inspector) to 0 for both the Title and Subtitle. If one of these labels is too long and you don't set the number of lines to 0, the table view cell won't adjust its size.
Forgot to remove tableView(heightForRowAt:indexPath:)
It could be the case that, like me, you accidentally forgot to remove the boilerplate code for the heightForRowAt method.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60.0
}
For anyone that maybe faced the same problem with me: I struggled for an hour to make it work. A simple UIlabel with top, bottom left and right constraints. I used the willDisplayCell to pass the data to the cell and this was causing problems in cells height. As long as I put that code in cellForRow everything worked just fine. Didn't understand why this happened, maybe the cell height was calculated before willDisplayCell was called so there was no content to make the right calculation. I was just wanted to mention and probably help someone with the same problem.
it was working for me fine in iOS 11 and above, while breaking in iOS 10
the solution was to add
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = 200 // a number and not UITableView.automaticDimension
also make sure you add "heightForRowAt" even if it works in iOS 11 and above, its needed if you support iOS 10
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
In my case i had two UITableViews, i had to modify this method as
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGFloat height = 0.0;
if (tableView == self.tableviewComments) {
return UITableViewAutomaticDimension;
}else if (tableView == self.tableViewFriends) {
height = 44.0;
}
return height;
}
Try this, Simple solution it's work for me,
In viewDidLoad write this code,
-(void)viewDidLoad
{
[super viewDidLoad];
self.tableView.estimatedRowHeight = 100.0; // for example. Set your average height
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
In cellForRowAtIndexPath write this code,
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] ;
}
cell.textLabel.numberOfLines = 0; // Set label number of line to 0
cell.textLabel.text=[[self.arForTable objectAtIndex:indexPath.row] valueForKey:#"menu"];
[cell.textLabel sizeToFit]; //set size to fit
return cell;
}
For my setup, I first double checked the constraints in the Storyboard for the UITableViewCell were unambiguous top to bottom, and then had to modify code in 2 places.
UITableViewDelegate's tableView(_:heightForRowAt:)
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
// return UITableViewAutomaticDimension for older than Swift 4.2
}
UITableViewDelegate's tableView(_:estimatedHeightForRowAt:)
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
// return UITableViewAutomaticDimension for older than Swift 4.2
}
Step 1 and 2, lets you to apply autosizing just for particular rows. For applying to the whole UITableView, use:
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = UITableView.automaticDimension
// return UITableViewAutomaticDimension for older than Swift 4.2
An uncommon solution, but one that may save others time: in cellForRowAtIndexPath make sure there is no delay in setting your label text. For some unknown reason, I had it wrapped in a DispatchQueue.main.async block, and this meant that the cell was being returned before the label text was actually set, so the cell and label never expanded to the size of the text.
In order to make UITableViewAutomaticDimension to work, you need to make sure all 4 corners' constraints is added to the superview. In most case, it works well with UILabel, but sometimes i find UITextView does the trick when UILabel not working well at the time. So try to switch to UITextView and make sure Scrolling Enabled is unchecked in storyboard, or you can also set it programmatically.
I had three horizontal items and the most left had a constraint to left, middle one had top, bottom, to the left item and to the right item. The right one had a right, and left to the middle item. The solution was to add additional left and right constraints to the middle item (my label) that were >= some number to the cell.
Make sure all views in the cell are constrained to the Content View and not to the cell.
As apple says:
lay out the table view cell’s content within the cell’s content view. To define the cell’s height, you need an unbroken chain of constraints and views (with defined heights) to fill the area between the content view’s top edge and its bottom edge.
In my case I did everything but it did not solve the problem for me, at last, I checked the lines of the label which was fixed to 3. Changing the line value to 0 solves the problem for me. so to tell you all it must be 0 and constraints Top, Bottom, Left, Right must be attached to work fine.
For recent version of ios and swift apple has changed the previously called UITableView.automaticDimension to UITableView().estimatedRowHeight, so to set the row height automatically use it like this:
tableView.estimatedRowHeight = 150 //random estimate manually to help the compiler before setting the estimate value.
tableView.rowHeight = UITableView().estimatedRowHeight
I've got a new solution..that's worked for me.
basically UITableViewAutomaticDimension renamed as UITableView.automaticDimension... I hope..it wo

Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

How do you use Auto Layout within UITableViewCells in a table view to let each cell's content and subviews determine the row height (itself/automatically), while maintaining smooth scrolling performance?
TL;DR: Don't like reading? Jump straight to the sample projects on GitHub:
iOS 8 Sample Project - Requires iOS 8
iOS 7 Sample Project - Works on iOS 7+
Conceptual Description
The first 2 steps below are applicable regardless of which iOS versions you are developing for.
1. Set Up & Add Constraints
In your UITableViewCell subclass, add constraints so that the subviews of the cell have their edges pinned to the edges of the cell's contentView (most importantly to the top AND bottom edges). NOTE: don't pin subviews to the cell itself; only to the cell's contentView! Let the intrinsic content size of these subviews drive the height of the table view cell's content view by making sure the content compression resistance and content hugging constraints in the vertical dimension for each subview are not being overridden by higher-priority constraints you have added. (Huh? Click here.)
Remember, the idea is to have the cell's subviews connected vertically to the cell's content view so that they can "exert pressure" and make the content view expand to fit them. Using an example cell with a few subviews, here is a visual illustration of what some (not all!) of your constraints would need to look like:
You can imagine that as more text is added to the multi-line body label in the example cell above, it will need to grow vertically to fit the text, which will effectively force the cell to grow in height. (Of course, you need to get the constraints right in order for this to work correctly!)
Getting your constraints right is definitely the hardest and most important part of getting dynamic cell heights working with Auto Layout. If you make a mistake here, it could prevent everything else from working -- so take your time! I recommend setting up your constraints in code because you know exactly which constraints are being added where, and it's a lot easier to debug when things go wrong. Adding constraints in code can be just as easy as and significantly more powerful than Interface Builder using layout anchors, or one of the fantastic open source APIs available on GitHub.
If you're adding constraints in code, you should do this once from within the updateConstraints method of your UITableViewCell subclass. Note that updateConstraints may be called more than once, so to avoid adding the same constraints more than once, make sure to wrap your constraint-adding code within updateConstraints in a check for a boolean property such as didSetupConstraints (which you set to YES after you run your constraint-adding code once). On the other hand, if you have code that updates existing constraints (such as adjusting the constant property on some constraints), place this in updateConstraints but outside of the check for didSetupConstraints so it can run every time the method is called.
2. Determine Unique Table View Cell Reuse Identifiers
For every unique set of constraints in the cell, use a unique cell reuse identifier. In other words, if your cells have more than one unique layout, each unique layout should receive its own reuse identifier. (A good hint that you need to use a new reuse identifier is when your cell variant has a different number of subviews, or the subviews are arranged in a distinct fashion.)
For example, if you were displaying an email message in each cell, you might have 4 unique layouts: messages with just a subject, messages with a subject and a body, messages with a subject and a photo attachment, and messages with a subject, body, and photo attachment. Each layout has completely different constraints required to achieve it, so once the cell is initialized and the constraints are added for one of these cell types, the cell should get a unique reuse identifier specific to that cell type. This means when you dequeue a cell for reuse, the constraints have already been added and are ready to go for that cell type.
Note that due to differences in intrinsic content size, cells with the same constraints (type) may still have varying heights! Don't confuse fundamentally different layouts (different constraints) with different calculated view frames (solved from identical constraints) due to different sizes of content.
Do not add cells with completely different sets of constraints to the same reuse pool (i.e. use the same reuse identifier) and then attempt to remove the old constraints and set up new constraints from scratch after each dequeue. The internal Auto Layout engine is not designed to handle large scale changes in constraints, and you will see massive performance issues.
For iOS 8 - Self-Sizing Cells
3. Enable Row Height Estimation
To enable self-sizing table view cells, you must set the table view’s
rowHeight property to UITableViewAutomaticDimension. You must also
assign a value to the estimatedRowHeight property. As soon as both of
these properties are set, the system uses Auto Layout to calculate the
row’s actual height
Apple: Working with Self-Sizing Table View Cells
With iOS 8, Apple has internalized much of the work that previously had to be implemented by you prior to iOS 8. In order to allow the self-sizing cell mechanism to work, you must first set the rowHeight property on the table view to the constant UITableView.automaticDimension. Then, you simply need to enable row height estimation by setting the table view's estimatedRowHeight property to a nonzero value, for example:
self.tableView.rowHeight = UITableView.automaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
What this does is provide the table view with a temporary estimate/placeholder for the row heights of cells that are not yet onscreen. Then, when these cells are about to scroll on screen, the actual row height will be calculated. To determine the actual height for each row, the table view automatically asks each cell what height its contentView needs to be based on the known fixed width of the content view (which is based on the table view's width, minus any additional things like a section index or accessory view) and the auto layout constraints you have added to the cell's content view and subviews. Once this actual cell height has been determined, the old estimated height for the row is updated with the new actual height (and any adjustments to the table view's contentSize/contentOffset are made as needed for you).
Generally speaking, the estimate you provide doesn't have to be very accurate -- it is only used to correctly size the scroll indicator in the table view, and the table view does a good job of adjusting the scroll indicator for incorrect estimates as you scroll cells onscreen. You should set the estimatedRowHeight property on the table view (in viewDidLoad or similar) to a constant value that is the "average" row height. Only if your row heights have extreme variability (e.g. differ by an order of magnitude) and you notice the scroll indicator "jumping" as you scroll should you bother implementing tableView:estimatedHeightForRowAtIndexPath: to do the minimal calculation required to return a more accurate estimate for each row.
For iOS 7 support (implementing auto cell sizing yourself)
3. Do a Layout Pass & Get The Cell Height
First, instantiate an offscreen instance of a table view cell, one instance for each reuse identifier, that is used strictly for height calculations. (Offscreen meaning the cell reference is stored in a property/ivar on the view controller and never returned from tableView:cellForRowAtIndexPath: for the table view to actually render onscreen.) Next, the cell must be configured with the exact content (e.g. text, images, etc) that it would hold if it were to be displayed in the table view.
Then, force the cell to immediately layout its subviews, and then use the systemLayoutSizeFittingSize: method on the UITableViewCell's contentView to find out what the required height of the cell is. Use UILayoutFittingCompressedSize to get the smallest size required to fit all the contents of the cell. The height can then be returned from the tableView:heightForRowAtIndexPath: delegate method.
4. Use Estimated Row Heights
If your table view has more than a couple dozen rows in it, you will find that doing the Auto Layout constraint solving can quickly bog down the main thread when first loading the table view, as tableView:heightForRowAtIndexPath: is called on each and every row upon first load (in order to calculate the size of the scroll indicator).
As of iOS 7, you can (and absolutely should) use the estimatedRowHeight property on the table view. What this does is provide the table view with a temporary estimate/placeholder for the row heights of cells that are not yet onscreen. Then, when these cells are about to scroll on screen, the actual row height will be calculated (by calling tableView:heightForRowAtIndexPath:), and the estimated height updated with the actual one.
Generally speaking, the estimate you provide doesn't have to be very accurate -- it is only used to correctly size the scroll indicator in the table view, and the table view does a good job of adjusting the scroll indicator for incorrect estimates as you scroll cells onscreen. You should set the estimatedRowHeight property on the table view (in viewDidLoad or similar) to a constant value that is the "average" row height. Only if your row heights have extreme variability (e.g. differ by an order of magnitude) and you notice the scroll indicator "jumping" as you scroll should you bother implementing tableView:estimatedHeightForRowAtIndexPath: to do the minimal calculation required to return a more accurate estimate for each row.
5. (If Needed) Add Row Height Caching
If you've done all the above and are still finding that performance is unacceptably slow when doing the constraint solving in tableView:heightForRowAtIndexPath:, you'll unfortunately need to implement some caching for cell heights. (This is the approach suggested by Apple's engineers.) The general idea is to let the Autolayout engine solve the constraints the first time, then cache the calculated height for that cell and use the cached value for all future requests for that cell's height. The trick of course is to make sure you clear the cached height for a cell when anything happens that could cause the cell's height to change -- primarily, this would be when that cell's content changes or when other important events occur (like the user adjusting the Dynamic Type text size slider).
iOS 7 Generic Sample Code (with lots of juicy comments)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path, depending on the particular layout required (you may have
// just one, or may have many).
NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.
// Note that this method will init and return a new cell if there isn't
// one available in the reuse pool, so either way after this line of
// code you will have a cell with the correct constraints ready to go.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the
// preferredMaxLayoutWidth needs to be set correctly. Do it at this
// point if you are NOT doing it within the UITableViewCell subclass
// -[layoutSubviews] method. For example:
// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path.
NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse
// identifier, creating a cell and storing it in the dictionary if one
// hasn't already been added for the reuse identifier. WARNING: Don't
// call the table view's dequeueReusableCellWithIdentifier: method here
// because this will result in a memory leak as the cell is created but
// never returned from the tableView:cellForRowAtIndexPath: method!
UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[YourTableViewCellClass alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Set the width of the cell to match the width of the table view. This
// is important so that we'll get the correct cell height for different
// table view widths if the cell's height depends on its width (due to
// multi-line UILabels word wrapping, etc). We don't need to do this
// above in -[tableView:cellForRowAtIndexPath] because it happens
// automatically when the cell is used in the table view. Also note,
// the final width of the cell may not be the width of the table view in
// some cases, for example when a section index is displayed along
// the right side of the table view. You must account for the reduced
// cell width.
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Do the layout pass on the cell, which will calculate the frames for
// all the views based on the constraints. (Note that you must set the
// preferredMaxLayoutWidth on multiline UILabels inside the
// -[layoutSubviews] method of the UITableViewCell subclass, or do it
// manually at this point before the below 2 lines!)
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell's contentView
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Add an extra point to the height to account for the cell separator,
// which is added between the bottom of the cell's contentView and the
// bottom of the table view cell.
height += 1.0;
return height;
}
// NOTE: Set the table view's estimatedRowHeight property instead of
// implementing the below method, UNLESS you have extreme variability in
// your row heights and you notice the scroll indicator "jumping"
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do the minimal calculations required to be able to return an
// estimated row height that's within an order of magnitude of the
// actual height. For example:
if ([self isTallCellAtIndexPath:indexPath]) {
return 350.0;
} else {
return 40.0;
}
}
Sample Projects
iOS 8 Sample Project - Requires iOS 8
iOS 7 Sample Project - Works on iOS 7+
These projects are fully working examples of table views with variable row heights due to table view cells containing dynamic content in UILabels.
Xamarin (C#/.NET)
If you're using Xamarin, check out this sample project put together by #KentBoogaart.
For iOS 8 above it's really simple:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 80
self.tableView.rowHeight = UITableView.automaticDimension
}
or
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableView.automaticDimension
}
But for iOS 7, the key is calculate the height after autolayout:
func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
cell.setNeedsLayout()
cell.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
return height
}
Important
If multiple lines labels, don't forget set the numberOfLines to 0.
Don't forget label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)
The full example code is here.
Swift example of a variable height UITableViewCell
Updated for Swift 3
William Hu's Swift answer is good, but it helps me to have some simple yet detailed steps when learning to do something for the first time. The example below is my test project while learning to make a UITableView with variable cell heights. I based it on this basic UITableView example for Swift.
The finished project should look like this:
Create a new project
It can be just a Single View Application.
Add the code
Add a new Swift file to your project. Name it MyCustomCell. This class will hold the outlets for the views that you add to your cell in the storyboard. In this basic example we will only have one label in each cell.
import UIKit
class MyCustomCell: UITableViewCell {
#IBOutlet weak var myCellLabel: UILabel!
}
We will connect this outlet later.
Open ViewController.swift and make sure you have the following content:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// These strings will be the data for the table view cells
let animals: [String] = [
"Ten horses: horse horse horse horse horse horse horse horse horse horse ",
"Three cows: cow, cow, cow",
"One camel: camel",
"Ninety-nine sheep: sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
"Thirty goats: goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]
// Don't forget to enter this in IB also
let cellReuseIdentifier = "cell"
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// delegate and data source
tableView.delegate = self
tableView.dataSource = self
// Along with auto layout, these are the keys for enabling variable cell height
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.animals[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
Important Note:
It is the following two lines of code (along with auto layout) that make the variable cell height possible:
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
Setup the storyboard
Add a Table View to your view controller and use auto layout to pin it to the four sides. Then drag a Table View Cell onto the Table View. And onto the Prototype cell, drag a Label. Use auto layout to pin the label to the four edges of the content view of the Table View Cell.
Important note:
Auto layout works together with the important two lines of code I mentioned above. If you don't use auto layout it isn't going to work.
Other IB settings
Custom class name and Identifier
Select the Table View Cell and set the custom class to be MyCustomCell (the name of the class in the Swift file we added). Also set the Identifier to be cell (the same string that we used for the cellReuseIdentifier in the code above.
Zero Lines for Label
Set the number of lines to 0 in your Label. This means multi-line and allows the label to resize itself based on its content.
Hook Up the Outlets
Control drag from the Table View in the storyboard to the tableView variable in the ViewController code.
Do the same for the Label in your Prototype cell to the myCellLabel variable in the MyCustomCell class.
Finished
You should be able to run your project now and get cells with variable heights.
Notes
This example only works for iOS 8 and after. If you are still needing to support iOS 7 then this won't work for you.
Your own custom cells in your future projects will probably have more than a single label. Make sure that you get everything pinned right so that auto layout can determine the correct height to use. You may also have to use vertical compression resistance and hugging. See this article for more about that.
If you are not pinning the leading and trailing (left and right) edges, you may also need to set the label's preferredMaxLayoutWidth so that it knows when to line wrap. For example, if you had added a Center Horizontally constraint to the label in the project above rather than pin the leading and trailing edges, then you would need to add this line to the tableView:cellForRowAtIndexPath method:
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
See also
Understanding Self Sizing Cells and Dynamic Type in iOS 8
Table View Cells with Varying Row Heights
UITableView example for Swift
I wrapped #smileyborg's iOS7 solution in a category
I decided to wrap this clever solution by #smileyborg into a UICollectionViewCell+AutoLayoutDynamicHeightCalculation category.
The category also rectifies the issues outlined in #wildmonkey's answer (loading a cell from a nib and systemLayoutSizeFittingSize: returning CGRectZero)
It doesn't take into account any caching but suits my needs right now. Feel free to copy, paste and hack at it.
UICollectionViewCell+AutoLayoutDynamicHeightCalculation.h
#import <UIKit/UIKit.h>
typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);
/**
* A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
*
* Many thanks to #smileyborg and #wildmonkey
*
* #see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
*/
#interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)
/**
* Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
*
* #param name Name of the nib file.
*
* #return collection view cell for using to calculate content based height
*/
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;
/**
* Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
*
* #param block Render the model data to your UI elements in this block
*
* #return Calculated constraint derived height
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;
/**
* Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;
#end
UICollectionViewCell+AutoLayoutDynamicHeightCalculation.m
#import "UICollectionViewCell+AutoLayout.h"
#implementation UICollectionViewCell (AutoLayout)
#pragma mark Dummy Cell Generator
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
[heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
return heightCalculationCell;
}
#pragma mark Moving Constraints
- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
[self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
[self removeConstraint:constraint];
id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}];
}
#pragma mark Height
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
NSParameterAssert(block);
block();
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return calculatedSize.height;
}
#end
Usage example:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
[(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
}];
return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}
Thankfully we won't have to do this jazz in iOS8, but there it is for now!
Here is my solution:
You need to tell the TableView the estimatedHeight before it loads the view. Otherwise it wont be able to behave like expected.
Objective-C
- (void)viewWillAppear:(BOOL)animated {
_messageField.delegate = self;
_tableView.estimatedRowHeight = 65.0;
_tableView.rowHeight = UITableViewAutomaticDimension;
}
Update to Swift 4.2
override func viewWillAppear(_ animated: Bool) {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 65.0
}
The solution proposed by #smileyborg is almost perfect. If you have a custom cell and you want one or more UILabel with dynamic heights then the systemLayoutSizeFittingSize method combined with AutoLayout enabled returns a CGSizeZero unless you move all your cell constraints from the cell to its contentView (as suggested by #TomSwift here How to resize superview to fit all subviews with autolayout?).
To do so you need to insert the following code in your custom UITableViewCell implementation (thanks to #Adrian).
- (void)awakeFromNib{
[super awakeFromNib];
for (NSLayoutConstraint *cellConstraint in self.constraints) {
[self removeConstraint:cellConstraint];
id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
NSLayoutConstraint *contentViewConstraint =
[NSLayoutConstraint constraintWithItem:firstItem
attribute:cellConstraint.firstAttribute
relatedBy:cellConstraint.relation
toItem:seccondItem
attribute:cellConstraint.secondAttribute
multiplier:cellConstraint.multiplier
constant:cellConstraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
Mixing #smileyborg answer with this should works.
An important enough gotcha I just ran into to post as an answer.
#smileyborg's answer is mostly correct. However, if you have any code in the layoutSubviews method of your custom cell class, for instance setting the preferredMaxLayoutWidth, then it won't be run with this code:
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
It confounded me for awhile. Then I realized it's because those are only triggering layoutSubviews on the contentView, not the cell itself.
My working code looks like this:
TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
Note that if you are creating a new cell, I'm pretty sure you don't need to call setNeedsLayout as it should already be set. In cases where you save a reference to a cell, you should probably call it. Either way it shouldn't hurt anything.
Another tip if you are using cell subclasses where you are setting things like preferredMaxLayoutWidth. As #smileyborg mentions, "your table view cell hasn't yet had its width fixed to the table view's width". This is true, and trouble if you are doing your work in your subclass and not in the view controller. However you can simply set the cell frame at this point using the table width:
For instance in the calculation for height:
self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:#"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);
(I happen to cache this particular cell for re-use, but that's irrelevant).
As long as your layout in your cell is good.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}
Update: You should use dynamic resizing introduced in iOS 8.
(for Xcode 8.x / Xcode 9.x read at the bottom)
Beware of the following issue in in Xcode 7.x, which might be a source of confusion:
Interface Builder does not handle auto-sizing cell set-up properly. Even if your constraints are absolutely valid, IB will still complain and give you confusing suggestions and errors. The reason is that IB is unwilling to change the row's height as your constraints dictate (so that the cell fits around your content). Instead, it keeps the row's height fixed and starts suggesting you change your constraints, which you should ignore.
For example, imagine you've set up everything fine, no warnings, no errors, all works.
Now if you change the font size (in this example I'm changing the description label font size from 17.0 to 18.0).
Because the font size increased, the label now wants to occupy 3 rows (before that it was occupying 2 rows).
If Interface Builder worked as expected, it would resize the cell's height to accommodate the new label height. However what actually happens is that IB displays the red auto-layout error icon and suggest that you modify hugging/compression priorities.
You should ignore these warnings. What you can* do instead is to manually change the row's height in (select Cell > Size Inspector > Row Height).
I was changing this height one click at a time (using the up/down stepper) until the red arrow errors disappear! (you will actually get yellow warnings, at which point just go ahead and do 'update frames', it should all work).
* Note that you don't actually have to resolve these red errors or yellow warnings in Interface Builder - at runtime, everything will work correctly (even if IB shows errors/warnings). Just make sure that at runtime in the console log you're not getting any AutoLayout errors.
In fact trying to always update row height in IB is super annoying and sometimes close to impossible (because of fractional values).
To prevent the annoying IB warnings/errors, you can select the views involved and in Size Inspector for the property Ambiguity choose Verify Position Only
Xcode 8.x / Xcode 9.x seems to (sometimes) be doing things differently than Xcode 7.x, but still incorrectly. For example even when compression resistance priority / hugging priority are set to required (1000), Interface Builder might stretch or clip a label to fit the cell (instead of resizing cell height to fit around the label). And in such a case it might not even show any AutoLayout warnings or errors. Or sometimes it does exactly what Xcode 7.x did, described above.
In case people are still having trouble with this. I wrote a quick blog post about using Autolayout with UITableViews Leveraging Autolayout For Dynamic Cell Heights as well as an open source component to help make this more abstract and easier to implement.
https://github.com/Raizlabs/RZCellSizeManager
To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement tableview dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
Objective C:
// in ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property IBOutlet UITableView * table;
#end
// in ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.table.dataSource = self;
self.table.delegate = self;
self.table.rowHeight = UITableViewAutomaticDimension;
self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Swift:
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// 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 label instance in UITableviewCell
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.
Note: If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.
Like #Bob-Spryn I ran into an important enough gotcha that I'm posting this as an answer.
I struggled with #smileyborg's answer for a while. The gotcha that I ran into is if you've defined your prototype cell in IB with additional elements (UILabels, UIButtons, etc.) in IB when you instantiate the cell with [[YourTableViewCellClass alloc] init] it will not instantiate all the other elements within that cell unless you've written code to do that. (I had a similar experience with initWithStyle.)
To have the storyboard instantiate all the additional elements obtain your cell with [tableView dequeueReusableCellWithIdentifier:#"DoseNeeded"] (Not [tableView dequeueReusableCellWithIdentifier:forIndexPath:] as this'll cause interesting problems.) When you do this all the elements you defined in IB will be instantiated.
Dynamic Table View Cell Height and Auto Layout
A good way to solve the problem with storyboard Auto Layout:
- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
static RWImageCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];
});
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
Another "solution": skip all this frustration and use a UIScrollView instead to get a result that looks and feels identical to UITableView.
That was the painful "solution" for me, after having put in literally 20+ very frustrating hours total trying to build something like what smileyborg suggested and failing over many months and three versions of App Store releases.
My take is that if you really need iOS 7 support (for us, it's essential) then the technology is just too brittle and you'll pull your hair out trying. And that UITableView is complete overkill generally unless you're using some of the advanced row editing features and/or really need to support 1000+ "rows" (in our app, it's realistically never more than 20 rows).
The added bonus is that the code gets insanely simple versus all the delegate crap and back and forth that comes with UITableView. It's just one single loop of code in viewOnLoad that looks elegant and is easy to manage.
Here are some tips on how to do it:
Using either Storyboard or a nib file, create a ViewController and associated root view.
Drag over a UIScrollView onto your root view.
Add constraints top, bottom, left, and right constraints to the top-level view so the UIScrollView fills the entire root view.
Add a UIView inside the UIScrollView and call it "container". Add top, bottom, left and right constraints to the UIScrollView (its parent). KEY TRICK: Also add an "Equal widths" constraint to link the UIScrollView and UIView.
NOTE: You will get an error "scroll view has ambiguous scrollable content height" and that your container UIView should have a height of 0 pixels. Neither error seems to matter when the app is running.
Create nib files and controllers for each of your "cells". Use UIView not UITableViewCell.
In your root ViewController, you essentially add all the "rows" to the container UIView and programmatically add constraints linking their left and right edges to the container view, their top edges to either the container view top (for the first item) or the previous cell. Then link the final cell to the container bottom.
For us, each "row" is in a nib file. So the code looks something like this:
class YourRootViewController {
#IBOutlet var container: UIView! //container mentioned in step 4
override func viewDidLoad() {
super.viewDidLoad()
var lastView: UIView?
for data in yourDataSource {
var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
UITools.addViewToTop(container, child: cell.view, sibling: lastView)
lastView = cell.view
//Insert code here to populate your cell
}
if(lastView != nil) {
container.addConstraint(NSLayoutConstraint(
item: lastView!,
attribute: NSLayoutAttribute.Bottom,
relatedBy: NSLayoutRelation.Equal,
toItem: container,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 0))
}
///Add a refresh control, if you want - it seems to work fine in our app:
var refreshControl = UIRefreshControl()
container.addSubview(refreshControl!)
}
}
And here's the code for UITools.addViewToTop:
class UITools {
///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
{
child.setTranslatesAutoresizingMaskIntoConstraints(false)
container.addSubview(child)
//Set left and right constraints so fills full horz width:
container.addConstraint(NSLayoutConstraint(
item: child,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: container,
attribute: NSLayoutAttribute.Left,
multiplier: 1,
constant: 0))
container.addConstraint(NSLayoutConstraint(
item: child,
attribute: NSLayoutAttribute.Trailing,
relatedBy: NSLayoutRelation.Equal,
toItem: container,
attribute: NSLayoutAttribute.Right,
multiplier: 1,
constant: 0))
//Set vertical position from last item (or for first, from the superview):
container.addConstraint(NSLayoutConstraint(
item: child,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: sibling == nil ? container : sibling,
attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 0))
}
}
The only "gotcha" I've found with this approach so far is that UITableView has a nice feature of "floating" section headers at the top of the view as you scroll. The above solution won't do that unless you add more programming but for our particular case this feature wasn't 100% essential and nobody noticed when it went away.
If you want dividers between your cells, just add a 1 pixel high UIView at the bottom of your custom "cell" that looks like a divider.
Be sure to turn on "bounces" and "bounce vertically" for the refresh control to work and so it seems more like a tableview.
TableView shows some empty rows and dividers under your content, if it doesn't fill the full screen where as this solution doesn't. But personally, I prefer if those empty rows weren't there anyway - with variable cell height it always looked "buggy" to me anyway to have the empty rows in there.
Here's hoping some other programmer reads my post BEFORE wasting 20+ hours trying to figure it out with Table View in their own app. :)
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension
I had to use dynamic views (setup views and constraints by code) and when I wanted to set preferredMaxLayoutWidth label's width was 0. So I've got wrong cell height.
Then I added
[cell layoutSubviews];
before executing
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
After that label's width was as expected and dynamic height was calculating right.
Let's say you have a cell with a subview, and you want the cell's height to be high enough to encompass the subview + padding.
1) Set the subview's bottom constraint equal to the cell.contentView minus the padding you want. Do not set constraints on the cell or cell.contentView itself.
2) Set either the tableView's rowHeight property or tableView:heightForRowAtIndexPath: to UITableViewAutomaticDimension.
3) Set either the tableView's estimatedRowHeight property or tableView:estimatedHeightForRowAtIndexPath: to a best guess of the height.
That's it.
If you do you layout programmatically, here is what to consider for iOS 10 using anchors in Swift.
There are three rules/ steps
NUMBER 1: set this two properties of tableview on viewDidLoad, the first one is telling to the tableview that should expect dynamic sizes on their cells, the second one is just to let the app calculate the size of the scrollbar indicator, so it helps for performance.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
NUMBER 2: This is important you need to add the subviews to the contentView of the cell not to the view, and also use its layoutsmarginguide to anchor the subviews to the top and bottom, this is a working example of how to do it.
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
}
private func setUpViews() {
contentView.addSubview(movieImageView)
contentView.addSubview(descriptionLabel)
let marginGuide = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
movieImageView.heightAnchor.constraint(equalToConstant: 80),
movieImageView.widthAnchor.constraint(equalToConstant: 80),
movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),
descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)
])
}
Create a method that will add the subviews and perform the layout, call it in the init method.
NUMBER 3: DON'T CALL THE METHOD:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
}
If you do it you will override your implementation.
Follow this 3 rules for dynamic cells in tableviews.
here is a working implementation
https://github.com/jamesrochabrun/MinimalViewController
If you have a long string. e.g. one which doesn't have a line break. Then you you might run into some problems.
The "alleged" fix is mentioned by the accepted answer and few other answers. You just need to add
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
I find Suragh's answer the most complete and concise, hence not confusing.
Though non explain why these changes are needed. Let's do that.
Drop the following code in to a project.
import UIKit
class ViewController: UIViewController {
lazy var label : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.backgroundColor = .red
lbl.textColor = .black
return lbl
}()
override func viewDidLoad() {
super.viewDidLoad()
// step0: (0.0, 0.0)
print("empty Text intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step1: (29.0, 20.5)
label.text = "hiiiii"
print("hiiiii intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step2: (328.0, 20.5)
label.text = "translatesAutoresizingMaskIntoConstraints"
print("1 translate intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step3: (992.0, 20.5)
label.text = "translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints"
print("3 translate intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step4: (328.0, 20.5)
label.text = "translatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints"
print("3 translate w/ line breaks (but the line breaks get ignored, because numberOfLines is defaulted to `1` and it will force it all to fit into one line! intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step5: (328.0, 61.0)
label.numberOfLines = 0
print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step6: (98.5, 243.5)
label.preferredMaxLayoutWidth = 100
print("3 translate w/ line breaks | '0' numberOfLines | preferredMaxLayoutWidth: 100 intrinsicContentSize: \(label.intrinsicContentSize)")
setupLayout()
}
func setupLayout(){
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
Note that I haven't added any size constraints. I've only added centerX, centerY constraints. But still the label will be sized correctly Why?
Because of contentSize.
To better process this, first keep step0, then comment out out steps 1-6. Let setupLayout() stay. Observe the behavior.
Then uncomment step1, and observe.
Then uncomment step2 and observe.
Do this until you've uncommented all 6 steps and observed their behaviors.
What can conclude from all this? What factors can change the contenSize?
Text Length: If you have a longer text then your intrinsicContentSize's width will increase
Line breaks: If you add \n then the intrinsicContentSize's width will the maximum width of all lines. If one line has 25 characters, another has 2 characters and another has 21 characters then your width will be calculated based the 25 characters
Number of allowed lines: You must set the numberOfLines to 0 otherwise the you won't have multiple lines. Your numberOfLines will adjust your intrinsicContentSize's height
Making adjustments: Imagine that based on your text, your intrinsicContentSize's width was 200 and height was 100, but you wanted to limited the width to the label's container what are you going to do? The solution is to set it to a desired width. You do that by setting preferredMaxLayoutWidth to 130 then your new intrinsicContentSize will have a width of roughly 130. The height would obviously be more than 100 because you'd need more lines.
That being said if your constraints are set correctly then you won't need to use this at all! For more on that see this answer and its comments. You only need to use preferredMaxLayoutWidth if you don't have constraints restricting the width/height as in one might say "don't wrap the text unless it exceeds the preferredMaxLayoutWidth". But with 100% certainty if you set the leading/trailing and numberOfLines to 0 then you're good! Long story short most answers here which recommend using it are WRONG! You don't need it. Needing it is a sign that your constraints are not set correctly or that you just don't have constraints
Font Size: Also note that if you increase your fontSize then the intrinsicContentSize's height will increase. I didn't show that in my code. You can try that on your own.
So back to your tableViewCell example:
All you need to do is:
set the numberOfLines to 0
constrain the label correctly to the margins/edges
There is no need to set preferredMaxLayoutWidth.
In my case i have to create a custom cell with a image which is coming from server and can be of any width and height. And two UILabels with dynamic size(both width & height)
i have achieved the same here in my answer with autolayout and programmatically:
Basically above #smileyBorg answer helped but systemLayoutSizeFittingSize never worked for me, In my approach :
1. No use of automatic row height calculation property. 2.No use of estimated height 3.No need of unnecessary updateConstraints. 4.No use of Automatic Preferred Max Layout Width. 5. No use of systemLayoutSizeFittingSize (should have use but not working for me, i dont know what it is doing internally), but instead my method -(float)getViewHeight working and i know what it's doing internally.
Is it possible to have differing heights in a UITableView Cell when I use several different ways of displaying the cell?
In my case, the padding was because of the sectionHeader and sectionFooter heights, where storyboard allowed me to change it to minimum 1. So in viewDidLoad method:
tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
I just did some dumb try and error with the 2 values of rowHeight and estimatedRowHeight and just thought it might provide some debugging insight:
If you set them both OR only set the estimatedRowHeight you will get the desired behavior:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1
It's suggested that you do your best to get the correct estimate, but the end result isn't different. It will just affect your performance.
If you only set the rowHeight ie only do:
tableView.rowHeight = UITableViewAutomaticDimension
your end result would not be as desired:
If you set the estimatedRowHeight to 1 or smaller then you will crash regardless of the rowHeight.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1
I crashed with the following error message:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
...some other lines...
libc++abi.dylib: terminating with uncaught exception of type
NSException
With regard to the accepted answer by #smileyborg, I have found
[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]
to be unreliable in some cases where constraints are ambiguous. Better to force the layout engine to calculate the height in one direction, by using the helper category on UIView below:
-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
CGFloat h = size.height;
return h;
}
Where w: is the width of the tableview
Simply add these two functions in your viewcontroller it will solve your problem. Here, list is a string array which contain your string of every row.
func tableView(_ tableView: UITableView,
estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])
return (tableView.rowHeight)
}
func calculateHeight(inString:String) -> CGFloat
{
let messageString = input.text
let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]
let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)
let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)
let requredSize:CGRect = rect
return requredSize.height
}
swift 4
#IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var tableView: UITableView!
private var context = 1
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
}
// Added observer to adjust tableview height based on the content
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.context{
if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize{
print("-----")
print(size.height)
tableViewHeightConstraint.constant = size.height + 50
}
}
}
//Remove observer
deinit {
NotificationCenter.default.removeObserver(self)
}
If the cell height is dynamic by the content, you should precisely count it out and then return the height value before the cell is rendered. An easy way is to define the counting method in the table view cell code for controller to call at the table cell height delegate method. Don't forget to count out the real cell frame width (default is 320) if the height is rely on the width of the table or screen. That is, in the table cell height delegate method, use cell.frame to correct the cell width first, then call the counting height method defined in the cell to get the suitable value and return it.
PS. The code for generating cell object could be defined in another method for different table view cell delegate method to call.
UITableView.automaticDimension can be set via Interface Builder:
Xcode > Storyboard > Size Inspector
Table View Cell > Row Height > Automatic
yet another iOs7+iOs8 solution in Swift
var cell2height:CGFloat=44
override func viewDidLoad() {
super.viewDidLoad()
theTable.rowHeight = UITableViewAutomaticDimension
theTable.estimatedRowHeight = 44.0;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
cell2height=cell.contentView.height
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if #available(iOS 8.0, *) {
return UITableViewAutomaticDimension
} else {
return cell2height
}
}

Resources