How do you use Auto Layout within UITableViewCell
s in a table view to let each cell's content and subviews determine the row height (itself/automatically), while maintaining smooth scrolling performance?
Ios – Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
autolayoutiosrow-heightuitableview
Related Topic
- Ios – UIScrollView Scrollable Content Size Ambiguity
- Ios – Emulating aspect-fit behaviour using AutoLayout constraints in Xcode 6
- C# – Problem with clearing a List
- Ios – Specifying one Dimension of Cells in UICollectionView using Auto Layout
- R – How to get the absolute path of the swf file in Actionscript
Best Answer
TL;DR: Don't like reading? Jump straight to the sample projects on GitHub:
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'scontentView
! 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.
updateConstraints
method of your UITableViewCell subclass. Note thatupdateConstraints
may be called more than once, so to avoid adding the same constraints more than once, make sure to wrap your constraint-adding code withinupdateConstraints
in a check for a boolean property such asdidSetupConstraints
(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 theconstant
property on some constraints), place this inupdateConstraints
but outside of the check fordidSetupConstraints
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.
For iOS 8 - Self-Sizing Cells
3. Enable Row Height Estimation
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 constantUITableView.automaticDimension
. Then, you simply need to enable row height estimation by setting the table view'sestimatedRowHeight
property to a nonzero value, for example: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 (inviewDidLoad
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 implementingtableView: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 theUITableViewCell
'scontentView
to find out what the required height of the cell is. UseUILayoutFittingCompressedSize
to get the smallest size required to fit all the contents of the cell. The height can then be returned from thetableView: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 callingtableView: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 (inviewDidLoad
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 implementingtableView: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)
Sample Projects
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.