This article translated from: StackOverflow
Someone asked a question on the StackOverflow:
How do I use AutoLayout in UITableViewCell to implement cell content and sub-views to automatically calculate row heights and to keep smooth scrolling?
This issue has been 300+ support and 450+ collection, the answer has been 730+ support, very detailed how to achieve iOS8 on iOS7 and UITableView dynamic row high function, And the answer to the realization of uicollectionview dynamic row height also has reference significance. So here is a translation of this answer, I hope to be helpful to everyone. The following is a full-text translation of the answer:
The answer is slightly longer, and if you don't like to read it, you can look directly at the code for both examples:
- Sample code for IOS8-iOS8 support
- Example code for IOS7-ios7+
Core Concepts
Regardless of which iOS version you are developing, the first two steps in the following steps are required:
1. Set the layout constraint conditions
In the UITableViewCell subclass, add a layout constraint that allows the edge of the cell child view to be fixed (PIN) to the edge of the cell's Contentview (most importantly with the top and bottom margin constraints). Note: Do not pin the child view's margin constraint to the cell itself, only to the cell's Contentview! Make sure that the content compression resistance (compression resistance) and the adsorption constraint (hugging constraints) in the vertical direction of each sub-view are not overwritten by the higher precedence constraints you add, so that the intrinsic content size of these sub-views ( intrinsic content size) to drive the height of the contentview. (Not read?) Point here. )
Remember, the point is to have a vertical link between the child view of the cell and the contentview so that they can "exert pressure" on the Contentview, so that the contentview expands to fit their dimensions. Here's a cell and some sub-views as examples to show you some (not all!). What the layout constraints should look like:
As you can imagine, as more text is added to the label "multi-line body" in the example above, it needs to be raised vertically to fit the text, which effectively forces the cell to increase in height. (Of course, you need to set the constraints correctly to make it work properly!) )
How to set the correct constraints is absolutely the hardest and most important part of using AutoLayout to achieve dynamic row heights. If it's wrong, it might not work-so don't worry, take it slow! I recommend that you use code to set layout constraints so that you know exactly where each layout constraint is added and that it's easier to debug when something goes wrong. Especially if you use some good open source libraries, you can make it as simple and intuitive to set constraints in code as with Interface Builder and more powerful. There is also a dedicated library designed and maintained by me: https://github.com/smileyborg/PureLayout
- If you use code to set the layout constraints, you should do it in the Updateconstraints method of the UITableViewCell subclass once. Note that updateconstraints may not be called more than once, so avoid adding the same layout constraints repeatedly. In Updateconstraints, the code that adds the layout constraint can be wrapped in an IF condition statement (such as a Boolean property called Didsetupconstraints, which runs once to add the layout constraint's code and sets it to Yes). To ensure that the same layout constraints are not added repeatedly. In addition, updating code that already has layout constraints (such as adjusting the constant property of the layout constraints) should also be placed in updateconstraints, but outside of the didsetupconstraints conditional statement, This ensures that each invocation will be executed.
2. Cell uses a unique reuse identifier
Within the cell, a specific cell reuse identifier is used for each specific set of constraints. In other words, if the cell has many different layouts, each layout should have its corresponding reuse identifier. (When the cell has a number of different numbers of sub-views, or if the child views are laid out in a unique way, in these cases you need to use a new reuse identifier.) )
For example, to display an email message in a cell, there might be 4 unique layouts: The first, only the subject message, the second, the message with the subject and the body, and the third, the message with the subject and picture attachment, and the fourth message with the subject, body, and picture attachments. Each layout requires a completely different layout constraint to be implemented. Therefore, once the cell is initialized and the layout constraint is added to a cell of either of these types, the cell should have a unique reuse identifier to specify the cell type. This means that when you dequeue reuse a cell, the layout constraints for that type of cell have been added and can be used directly.
Note that cells with the same layout constraints may still have different heights due to the intrinsic content size! Do not confuse layouts (different constraints) and different views of different content sizes (calculated with the same layout constraints) the two concepts of frame, which are fundamentally different two things.
- Do not drop cells with different layout constraints into the same reuse pool (that is, use the same reuse identifier), then remove the old constraint after each dequeue and re-add the constraint from the beginning. The auto layout engine is not designed to handle large-scale constraint changes and you will see a lot of performance issues.
Ios8-self-sizing Cells3. Enable Estimated Row height
On IOS8, Apple has built in many things that were difficult to achieve before iOS8. In order for the cell to implement the self-sizing mechanism, the TableView's RowHeight property must first be set to the constant uitableviewautomaticdimension. Then, simply set the TableView's Estimatedrowheight property to a value other than 0 to turn on the line height estimation function, for example:
Self.TableView.RowHeight = uitableviewautomaticdimension; Self.TableView.Estimatedrowheight = 44.0; //Set to a value close to the "average" row height |
This provides a temporary estimate of the row height for a cell on the tableview that is not yet displayed on the screen. Then, when the cell is about to roll into the screen range, the actual height is calculated. To determine the actual height of each row, TableView automatically causes each cell to be based on the known fixed width of its contentview (the width of the tableview, minus any additional, like section Index or Accessoryview these widths) and the automatic layout constraint rules that are added to Contentview and its sub-views to calculate the height of the contentview. Once the true row height is computed, the old estimated row height is updated to this true row height (and any other changes that need to be made to TableView's contentsize or Contentoffset are automatically completed for you).
In general, the row height estimates do not need to be too precise-it is only used to correct the size of the scrollbar in the TableView, and when you slide the cell on the screen, the TableView can adjust the scroll bar well even if the estimate is inaccurate. Set the Estimatedrowheight property of TableView to (in Viewdidload or similar method) a constant value close to the "average" row height. only when the line height changes very extreme (such as an order of magnitude), the scroll bar will produce a "jump" phenomenon. At this time, you should implement the Tableview:estimatedheightforrowatindexpath: method to return a more accurate estimate for each row.
IOS7 Support (self-adaptive cell size required)3. Complete a complete layout process & Calculate the cell height
First, an off-screen (offscreen) instance is initialized for each cell, and a corresponding cell instance is instantiated for each reuse identifier, which is used exclusively for height calculations. (an off-screen reference to a cell is stored in a property or instance variable of the view controller, and the cell is never used as a Tableview:cellforrowatindexpath: The return value of the method is actually rendered on the screen.) Next, the contents of the cell (for example, text, pictures, and so on) must also be exactly the same as what is displayed in Table view.
Then, force the cell to immediately update the layout of the child view, and then call Systemlayoutsizefittingsize with the cell's Contentview: method to calculate how much height the cell requires. Use the Uilayoutfittingcompressedsize parameter to get the minimum size needed to fit all the content in the cell. The height can then be used as the Tableview:heightforrowatindexpath: The return value of the method.
4. Using the estimated row height
If your table view is more than dozens of rows, you will find that the automatic layout constraint solves the main thread quickly when the table view is loaded for the first time. Because, during the first load process, each row calls the Tableview:heightforrowatindexpath: Method (in order to calculate the size of the scrollbar).
In IOS7, you can (and absolutely should) use the Estimatedrowheight property of Table view. This provides a temporary estimated row height value for cells that are not yet in the screen range. Then, when these cells are about to roll into the screen range, the actual row height values are computed (via Tableview:heightforrowatindexpath: Method), and the estimated rows are replaced.
In general, the row height estimates do not need to be too precise-it is only used to correct the size of the scrollbar in the TableView, and when you slide the cell on the screen, the TableView can adjust the scroll bar well even if the estimate is inaccurate. Set the Estimatedrowheight property of TableView to (in Viewdidload or similar method) a constant value close to the "average" row height. only when the line height changes very extreme (such as an order of magnitude), the scroll bar will produce a "jump" phenomenon. At this time, you should implement the Tableview:estimatedheightforrowatindexpath: method to return a more accurate estimate for each row.
5. Cache row Height (if required)
If you have done all of the above mentioned, but Tableview:heightforrowatindexpath: performance is still slow unacceptable. Unfortunately, you need to do some caching for line heights (this is an improvement suggestion from Apple's engineers). The general idea is to have the automatic layout engine parse the constraints for the first time, then cache the computed rows high, and all subsequent requests for the cell's height will return the cached value. Of course, the key is also to ensure that any situation that causes cell height changes occurs when you clear the cached row height-This usually occurs when the contents of the cell change or when other significant events occur (such as a slider bar where the user adjusts the dynamic type text size).
IOS7 Sample code (with detailed comments)
-(UITableViewCell *) TableView: (UITableView *) TableView Cellforrowatindexpath: (Nsindexpath *) Indexpath { Judge the reuse identifier of the indexpath corresponding cell, Depending on the specific layout requirements (there may be only one, or multiple) NSString *reuseidentifier = ...; Remove the cell that corresponds to the reuse identifier. Note that if there are no cells available in the reuse pool (reuse pools), this method initializes and returns a completely new cell, So anyway, after this line of code, you can get a layout constraint that is fully prepared and can be used directly by the cell. UITableViewCell *cell = [TableView dequeuereusablecellwithidentifier:reuseidentifier]; Configure the cell with Indexpath corresponding data content, for example: Cell.textLabel.text = Sometextforthiscell; // ... Make sure that the layout constraints of the cell are set, because it may have just been created. Use the following two lines of code if you have already set the constraint in the cell's Updateconstraints method: [Cell setneedsupdateconstraints]; [Cell updateconstraintsifneeded]; If you are using multiple lines of Uilabel, do not forget that the preferredmaxlayoutwidth needs to be set correctly. If you do not set it in the cell's-[layoutsubviews] method, set it here. For example: Cell.multiLineLabel.preferredMaxLayoutWidth = Cgrectgetwidth (tableview.bounds); return cell; } -(CGFloat) TableView: (UITableView *) TableView Heightforrowatindexpath: (Nsindexpath *) Indexpath { Judge the reuse identifier of the indexpath corresponding cell, NSString *reuseidentifier = ...; Remove the cell that corresponds to the reuse identifier from the cell dictionary. If not, create a new one and store it in the dictionary. Warning: Do not invoke the Dequeuereusablecellwithidentifier: method of the Table view, as this will cause the cell to be created but not tableview:cellforrowatindexpath: method returns, causing a memory leak! UITableViewCell *cell = [Self.offscreencells objectforkey:reuseidentifier]; if (!cell) { cell = [[Yourtableviewcellclass alloc] init]; [Self.offscreencells Setobject:cell Forkey:reuseidentifier]; } Configure the cell with Indexpath corresponding data content, for example: Cell.textLabel.text = Sometextforthiscell; // ... Make sure that the layout constraints of the cell are set, because it may have just been created. Use the following two lines of code if you have already set the constraint in the cell's Updateconstraints method: [Cell setneedsupdateconstraints]; [Cell updateconstraintsifneeded]; Set the cell width to be as wide as the width of the tableview. This is important. If the cell's height depends on the width of the table view (for example, multiple lines of uilabel are wrapped by word wrapping), So this makes it possible for table view of different widths to have the correct height of the cell based on its width. However, we do not need to do the same with the-[tableview:cellforrowatindexpath] method, This is done automatically because the cell is used in table view. Also note that in some cases the final width of the cell may not be equal to the width of the table view. For example, you must subtract this width when the section index is displayed to the right of table view. Cell.bounds = CGRectMake (0.0f, 0.0f, Cgrectgetwidth (tableview.bounds), Cgrectgetheight (cell.bounds)); The layout process that triggers the cell calculates the frame for all views based on layout constraints. (Note that you have to set the Preferredmaxlayoutwidth value for multiple lines of Uilabel in the cell's-[layoutsubviews] method; or set it up manually before the following 2 lines of code! ) [Cell setneedslayout]; [Cell layoutifneeded]; To get the true height of the cell's contentview. CGFloat height = [Cell.contentview systemlayoutsizefittingsize:uilayoutfittingcompressedsize].height; Add an extra 1pt height to the cell's split line. Because the dividing line is added between the bottom of the cell and the bottom of the Contentview. Height + = 1.0f; return height; } Note: You only need to implement this method unless the line is highly variable and you are clearly aware of the "jumping" of the scrollbar when scrolling, otherwise, use the TableView Estimatedrowheight property directly. -(CGFloat) TableView: (UITableView *) TableView Estimatedheightforrowatindexpath: (Nsindexpath *) Indexpath { Returns the estimated row height within an actual height order of magnitude, with the minimum amount of computation required. For example: // if ([self istallcellatindexpath:indexpath]) { return 350.0f; } else { return 40.0f; } } |
Sample Project
- Sample code for IOS8-iOS8 support
- Example code for IOS7-ios7+
UITableView cell dynamic layout and highly dynamic change with AutoLayout