The height of UITableViewCell calculates the height of uitableviewcell from the beginning of chaos to the intersection of Heaven and Earth.
[Original] UITableViewCell height calculation from chaos initial to Tianji jiaotai
This article is based on the highly adaptive computing problem of iOS UITableViewCell:
The UITableView control may be the most commonly used control in iOS (rolling view, cell reuse, and choppy optimization). Today we are not talking about these high-level topics, today's topic is cell-level computing.
* UITableViewCell height calculation in a traditional frame Layout
* UITableViewCell height calculation in AutoLayout (iOS6, 7)
* IOS8 wind extraction tour with UITableViewCell Height Calculation
* UITableViewCell Height Calculation
* Third-Party Library UITableView-FDTemplateLayoutCell source code analysis
The following demo is based on the cell height variable.
1. UITableViewCell height calculation in traditional frame Layout
1. The most traditional UITableViewCell method in history (known as stupid and old). I believe everyone has used this method. It is a pure frame layout and cell customization, when you manually input data and manually calculate the height of each cell line, the code is too embarrassing.
Let's go to the previous demo!
01-UITableViewCell-frame
In UITableViewCell (subCell), a static method is used to input data and manually calculate the height of the content.
When it comes to manual calculation of the content height, most of the cells actually calculate the specific width and height of UILabel. Calculate the width and height of UILabel Based on the content. Let's take a look at the specific API:
@interface NSString(UIStringDrawing)// Single line, no wrapping. Truncation based on the NSLineBreakMode.- (CGSize)sizeWithFont:(UIFont*)fontNS_DEPRECATED_IOS(2_0,7_0,"Use -sizeWithAttributes:");- (CGSize)sizeWithFont:(UIFont*)font forWidth:(CGFloat)width lineBreakMode:(NSLineBreakMode)lineBreakModeNS_DEPRECATED_IOS(2_0,7_0,"Use -boundingRectWithSize:options:attributes:context:");// Wrapping to fit horizontal and vertical size. Text will be wrapped and truncated using the NSLineBreakMode. If the height is less than a line of text, it may return// a vertical size that is bigger than the one passed in.// If you size your text using the constrainedToSize: methods below, you should draw the text using the drawInRect: methods using the same line break mode for consistency- (CGSize)sizeWithFont:(UIFont*)font constrainedToSize:(CGSize)sizeNS_DEPRECATED_IOS(2_0,7_0,"Use -boundingRectWithSize:options:attributes:context:");// Uses NSLineBreakModeWordWrap- (CGSize)sizeWithFont:(UIFont*)font constrainedToSize:(CGSize)size lineBreakMode:(NSLineBreakMode)lineBreakModeNS_DEPRECATED_IOS(2_0,7_0,"Use -boundingRectWithSize:options:attributes:context:");// NSTextAlignment is not needed to determine size
Here, Apple provides an NSString classification. We can pass in the corresponding string to calculate the label's adaptive width and height. In the end, we use sizeWithFont: A series of heavy-duty functions.
Calculate the content size of the label based on the string.
Use in code:
(NSString a traditional method sizeWithFont :) to calculate the new frame of the label, then update the layout, and then return a pre-calculated height value.
+ (CGFloat)calulateHeightWithtTitle:(NSString*)title{CGFloatheight =20;CGSizelabelSize = [titlesizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:CGSizeMake(300,500)];height = height + labelSize.height;returnheight;}
The final method is called in:
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{ return [HomeCell calulateHeightWithtTitle:self.dataArray[indexPath.row]];}
And return the float value as the height of the cell.
Ii. Calculation of UITableViewCell height in AutoLayout (iOS6, 7)
1. The second method is described below. cell height calculation under automatic layout is generally attributed to the layout of UILabel, by default, no frame of the view needs to be specified in the Automatic Layout mode. After the corresponding constraints are set, the label automatically completes the Layout Based on the content. To put it bluntly, first go to the trial version demo.
08-AutoLayoutCellHeight_ios7
As described above, the layout time of the traditional frame is mainly to manually calculate the label width and height in the cell by some column hands, and then re-layout the subView in the cell, finally, we can get an overall height as the actual height of the cell. How can we achieve this in automatic layout? First, the concept of Automatic Layout is changed. There is no so-called coordinate width or height in the automatic layout, and only corresponding constraints exist. For UILabel, the label automatically adjusts the label size according to the content to display the corresponding content. First, let's take a look at the changes that have taken place after the iOS6 of UILabel:
// Support for constraint-based layout (auto layout)// If nonzero, this is used when determining -intrinsicContentSize for multiline labels@property(nonatomic)CGFloat preferredMaxLayoutWidthNS_AVAILABLE_IOS(6_0);
When you see the official watch, it basically means almost the same thing. This is actually used in autolayout, which roughly means setting a layout time-first width for Multiple labels.
Check the changes in the UIView.
@interfaceUIView (UIConstraintBasedLayoutFittingSize)/* The size fitting most closely to targetSize in which the receiver's subtree can be laid out while optimally satisfying the constraints. If you want the smallest possible size, pass UILayoutFittingCompressedSize; for the largest possible size, pass UILayoutFittingExpandedSize.Also see the comment for UILayoutPriorityFittingSizeLevel.*/- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSizeNS_AVAILABLE_IOS(6_0);// Equivalent to sending -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: with UILayoutPriorityFittingSizeLevel for both priorities.
It means that the current size relationship can best adapt to the receiver's subtree while meeting the adaptive constraints. If you want a minimum size, set it to UILayoutFittingCompressedSize. Otherwise, set it: UILayoutFittingExpandedSize.
Practical applications:
Adaptive cell height in Automatic Layout. This tutorial relies entirely on storybord, which is still in the Code UI field. You need to change your mind.
(1) create a storyboard, initialize the tableview and cell output ports, and prepare cell constraints,
There is only one label on the cell. The label constraint is as follows. Generally, the upper, lower, and lower sides are each added with a constraint. In the future, the corresponding content text will be placed in the label, adaptive height (do not forget to set the cell identifier ).
(2) partially implement the processing code
Some proxy methods in ViewController
-(NSInteger) numberOfSectionsInTableView :( UITableView *) tableView {return1;}-(NSInteger) tableView :( UITableView *) tableView numberOfRowsInSection :( NSInteger) section {return [self. dataArraycount];}-(UITableViewCell *) tableView :( UITableView *) tableView cellForRowAtIndexPath :( NSIndexPath *) indexPath {static NSString * handle = @ "HomeCell"; HomeCell * cell = [tableView handle: cellIdentifier]; cell. content. text = [self. dataArray objectAtIndex: indexPath. row]; CGFloat preMaxWaith = [UIScreen mainScreen]. bounds. size. width-108; [cell. contentset PreferredMaxLayoutWidth: preMaxWaith]; [cell. contentlayout IfNeeded]; returncell;}-(CGFloat) tableView :( UITableView *) tableView labels :( NSIndexPath *) indexPath {staticHomeCell * cell = nil; static dispatch_once _ tonceToken; // It takes only one dispatch_once (& onceToken, ^ {cell = (HomeCell *) [tableView metadata: @ "HomeCell"] ;}); // calculateCGFloatheight = [cell calulateHeightWithtTitle: [self. dataArray objectAtIndex: indexPath. row] desrip: [self. dataArray objectAtIndex: indexPath. row]; returnheight;} HomeCell. m-(CGFloat) calulateHeightWithtTitle :( NSString *) title desrip :( NSString *) descrip {// CGFloat preMaxWaith = [UIScreen mainScreen]. bounds. size. width-108; [self. contentset PreferredMaxLayoutWidth: preMaxWaith]; // [self. titleLabel setText: title]; // This is also important [self. content layoutIfNeeded]; [self. content setText: descrip]; [self. contentView layoutIfNeeded]; CGSizesize = [self. contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize]; // Adding 1 is the key returnsize. height + 1.0f ;}
Automatically layout cell height calculation OK !!
Iii. iOS8 wind extraction tour with UITableViewCell Height Calculation
1. When it comes to iOS8, if you want to calculate the cell height under iOS8, there will be fewer and fewer code and easier work, the iOS 8, which seems to be especially human, also has many pitfalls in its background (for specific reasons, see the explanation below ).
First, the iOS computing cell height experience demo:
02-AutoLayout-iOS8-
Computing cell height in iOS8 is easier than previous versions.
(1) drag the corresponding VC and cell for the Story version, and then the constraints are as follows:
In general, it is similar to the constraint in 2, respectively, setting the constraint that the label is four weeks away. (This article is intended to achieve the same effect, and compare and optimize the implementation methods and advantages and disadvantages in different versions .)
After setting Constraints
(2). cell height calculation code of iOS8
Set tableview attributes
self.tableView.estimatedRowHeight=44.0;self.tableView.rowHeight=UITableViewAutomaticDimension;
Now, iOS8cell highly adaptive computing is OK !! It's that simple...
Iv. Overall UITableViewCell Height Calculation
Before introducing this topic, create a table:
HeightForRowAtIndexPath: cell height calculation count:
HeightForRowAtIndexPath: cell computing comparison
After iOS7, tableview provides the estimatedHeightForRowAtIndexPathCount API, which affects the number of method calls in cell height calculation.
The following describes estimatedHeightForRowAtIndexPathCount:
// Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.// If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.- (CGFloat)tableView:(UITableView*)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath*)indexPathNS_AVAILABLE_IOS(7_0);
In apple's documentation, it probably means estimatedHeight can quickly estimate the height of a cell to make table loading faster. In general, tableview is the rendering time. It will first calculate the height of each cell based on the content, and then calculate a contentsize (tableview inherits from scrollview) of tableview ), however, if there are 10 thousand rows of data, the calculation process will be very slow, thus affecting the table load speed. We can give the cell (except the cell to be displayed on the screen currently) set an estimated height value, which greatly reduces the loss and overhead of the computing height.
It can be seen that tableview of iOS7 has the caching function for cell height. When it is crossed to the bottom, it slides from the bottom to the top for a time. heightForRowAtIndexPath: the number of cell calls is 0, the height of the cell already exists in the memory. When the Height Estimation Method time (estimatedHeightForRowAtIndexPathCount) is disabled, heightForRowAtIndexPath: the number of cell calls is very large (we usually calculate the height of the cell Based on the content here, or update the cell without various view constraints ), if frequent calls in this process consume a lot of performance, and even more tragic events cause choppy tableview, this is the most intolerable. When the high estimation time is enabled, a fixed value is returned when the high estimation is enabled. In this case, the number of heightForRowAtIndexPath: cell calls is greatly reduced, and the work of high calculation is greatly reduced. This is what we want.
In addition, there is a problem that may be ignored in this place. When our project is migrated from the original iOS7 to 8 and then to 9, if this place is not further optimized, the previous code runs in the new system and the result is not desired. To be compatible with cell high computing in all systems, we recommend a new tool.
UITableView-FDTemplateLayoutCell
Refer to blog: The specific tutorial is as follows:
(1) FDTemplateLayoutCell tutorial
1. Download The FDTemplateLayoutCell third-party library and import the project
2. Import the header file
3. Use FD to implement the heightForRowAtIndexPath method, as shown below:
4. Success. fd is used to solve the cell height calculation problem in iOS 6, 7, 8, and 9 at a time. FD uses its own cache mechanism, you do not need to call heightForRowAtIndexPath multiple times to calculate the cell height overhead.
1. The reason why FDTemplateLayoutCell is compatible with tableview in all system versions is that it maintains its own cell high cache and effectively utilizes the tableview high estimation function. The new cell height cache policy is defined, which solves the difficulty that only the system in iOS7 can actively cache the cell height. The cache height can also be used in FDiOS8 and 9.
2. Enable the UITableView Height Estimation Function to optimize the total number of calls to heightForRowAtIndexPath
(TableView: estimatedHeightForRowAtIndexPath: NS_AVAILABLE_IOS (7_0 );)
It can be seen from the above that estimatedHeightForRowAtIndexPath is only available in iOS7, and iOS6 does not have this proxy. This time is not only required for iOS to support iOS7 + or above. The answer is certainly not, the system's API has already been optimized. estimatedHeightForRowAtIndexPath can be ignored by default under iOS6, without exception caused by version issues. The high computing strategy in iOS6 is a bit similar to that in iOS8 and 9. Using FD's own cache can effectively reduce the overhead brought by the high computing cell.
5. FDTemplateLayoutCell source code analysis
When talking about FD, first familiarize yourself with a previous knowledge point, and sort out iOS knowledge points-RunLoop. It may be a bit old-fashioned, or some children's shoes may be dizzy. In fact, most third-party libraries in iOS are essentially runtime (this is called reflact in java, java contains AOP, which is similar to iOS.) black magic and other things such as CF are used to steal days and get flowers and trees.
Conclusion: runloop in iOS
1. nsunloop provides object-oriented APIs, but these APIs are NOT thread-safe.
2. CFRunLoopRef provides pure C function APIs, all of which are thread-safe.
The nsunloop is provided by cocoa, which may be frequently used by most people, update images that have been downloaded asynchronously in cell, enable a timer to append to the current application loop, and enable a resident thread;
CFRunLoopRef may be relatively unfamiliar. The starting point of CF is defined in CoreFoundation. It can be understood that each thread has a corresponding runloop, there can be multiple models in a runloop, and each Mode contains several source/Timer/Observe.
The time when the program is executed. Currently, runloop can only have one Model. In case of scenario switching, You need to exit the current Model and enter the next Model.
The system provides a total of five models:
NSDefaultRunLoopMode: Default Mode of the App. If ScrollView is not received, the main thread usually uses this Mode NSTrackingRunLoopMode: When ScroolView or its subclass is received, the main thread will switch to this mode to run. UIInitializationRunLoopMode: The first Mode used when the App is started. Nsunloopcommonmodes is a tag, which is essentially not a Mode. The default value is
Both NSDefaultRunLoopMode and NSTrackingRunLoopMode are bound to this tag. (Application Scenario: Sometimes we need to add an NSTimer in the RunLoop. In this case, we need to develop a Modes. Now we need to listen in the default mode, listening is also required in the rolling Mode, but only one Mode can be set. This can be set to CommonMode) GSEventReceiveRunLoopMode: accept the Mode inside the system, which is usually not possible. You can use different modes to process different events to maximize the performance and improve the performance.
Once we know this, we can make an article here. We found that the non-rolling time of UITableView (inheriting UIScrollView) is NSDefaultRunLoopMode, and the rolling time is NSTrackingRunLoopMode, we can register the observer to make the tableview do not scroll the time and then calculate the height of all cells. Once the tableview starts to scroll, we can get the time that has been computed in the cache pool, that is to say, the maximum time allowed for tableview not to scroll is to process the height of all cells, cache them, and wait until the sliding time of tableview takes priority from the cache, this part does its best to avoid the lag problem of cell height calculation by sliding side.
After completing this knowledge point, the next step is to handle the cache logic.
1. For FD, to maintain the cell height, you need to put the calculated cell height into a two-dimensional array (section row)
There is an maintainable NSMutableArray sections in FD. You can first understand that a nested array is a two-digit array, in the next time, the height of the row corresponding to a row under a section in tableview will be exists in this position,
2. The time for rendering tableView will still follow the heightForRowAtIndexPath method. We only need to directly obtain the stored height in the cache, in this case, the logic computing process without cell height has reached our goal.
The FD component has been well encapsulated and the fd calculation height method is called in heightForRowAtIndexPath,
- (CGFloat)fd_heightForCellWithIdentifier:(NSString*)identifier cacheByIndexPath:(NSIndexPath*)indexPath configuration:(void(^)(id))configuration{if(!identifier || !indexPath) {return0;}// Enable auto cache invalidation if you use this "cacheByIndexPath" API.if(!self.fd_autoCacheInvalidationEnabled) {self.fd_autoCacheInvalidationEnabled=YES;}// Enable precache if you use this "cacheByIndexPath" API.if(!self.fd_precacheEnabled) {self.fd_precacheEnabled=YES;// Manually trigger precache only for the first time.[selffd_precacheIfNeeded];}// Hit the cacheif([self.fd_cellHeightCachehasCachedHeightAtIndexPath:indexPath]) {[selffd_debugLog:[NSStringstringWithFormat:@"hit cache - [%@:%@] %@",@(indexPath.section),@(indexPath.row),@([self.fd_cellHeightCachecachedHeightAtIndexPath:indexPath])]];return[self.fd_cellHeightCachecachedHeightAtIndexPath:indexPath];}// Call basic height calculation method.CGFloatheight = [selffd_heightForCellWithIdentifier:identifierconfiguration:configuration];[selffd_debugLog:[NSStringstringWithFormat:@"cached - [%@:%@] %@",@(indexPath.section),@(indexPath.row),@(height)]];// Cache it[self.fd_cellHeightCachecacheHeight:heightbyIndexPath:indexPath];returnheight;}
In this step, we can basically see the Usage Policy of FD. First, we start the operation of [selffd_precacheIfNeeded] (this process is a pre-calculated cell height operation, which will be explained later ), the next process is to read the cell height from the cache pool based on IndexPath (cell height pre-stored in a simulated two-dimensional array). If the cache hits, the cell height is directly returned. Otherwise, execute:
// Call basic height calculation method.CGFloatheight = [selffd_heightForCellWithIdentifier:identifierconfiguration:configuration];
Manually calculate the height of a cell, and save the calculated result to the cache pool.
// Cache it[self.fd_cellHeightCachecacheHeight:heightbyIndexPath:indexPath];
Returns the final height.
3. Introduction to FD cache pool
FD uses the black magic of runloop in this place. By registering an observer, when tableview stops sliding, it will take the initiative to calculate the height of the remaining cell in the current data source, after calculation, it is stored in the cache pool. This call is in (2 ).
// Enable precache if you use this "cacheByIndexPath" API.if(!self.fd_precacheEnabled) {self.fd_precacheEnabled=YES;// Manually trigger precache only for the first time.[selffd_precacheIfNeeded];}
In this enable call, you can use some column methods to calculate the cell height for tableview without rolling time (the specific principle is omitted here), and save the calculation to the cache pool sections. sections is a variable array, the author shows that the memory storage element is an array of variable arrays (simulating a two-dimensional array). FD first adds an attribute sections to itself as a cache pool, adding attributes to a category through objc_setAssociatedObject is not described here,
[selfbuildHeightCachesAtIndexPathsIfNeeded:@[indexPath]];self.sections[indexPath.section][indexPath.row] =@(height);// Build every section array or row array which is smaller than given index path.[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath*indexPath,NSUIntegeridx,BOOL*stop) {NSAssert(indexPath.section>=0,@"Expect a positive section rather than '%@'.",@(indexPath.section));for(NSIntegersection =0; section <= indexPath.section; ++section) {if(section >=self.sections.count) {self.sections[section] =@[].mutableCopy;}}NSMutableArray*rows =self.sections[indexPath.section];for(NSIntegerrow =0; row <= indexPath.row; ++row) {if(row >= rows.count) {rows[row] =@(_FDTemplateLayoutCellHeightCacheAbsentValue);}}}];
Here, we mainly construct a cache pool to simulate a two-dimensional array by storing an NSMutableArray in the sections.
Using the section and row of indexPath as the subscript, the height is saved directly after the construction is completed:
Self. sections [indexPath. section] [indexPath. row] = @ (height );
The cache pool has ended.
4. At this point, the core function of FD has been explained, and the next step is to consider the processing problem of the added, deleted, modified, and inserted time of tableview. This series of actions will have a certain impact on the update of the cache pool, FD has been optimized to the maximum extent, and the runtime and swizzling magic will not be explained much.
dispatch_once(&onceToken, ^{SELselectors[] = {@selector(reloadData),@selector(insertSections:withRowAnimation:),@selector(deleteSections:withRowAnimation:),@selector(reloadSections:withRowAnimation:),@selector(moveSection:toSection:),@selector(insertRowsAtIndexPaths:withRowAnimation:),@selector(deleteRowsAtIndexPaths:withRowAnimation:),@selector(reloadRowsAtIndexPaths:withRowAnimation:),@selector(moveRowAtIndexPath:toIndexPath:)};for(NSUIntegerindex =0; indexSELoriginalSelector = selectors[index];SELswizzledSelector =NSSelectorFromString([@"fd_"stringByAppendingString:NSStringFromSelector(originalSelector)]);MethodoriginalMethod =class_getInstanceMethod(self, originalSelector);MethodswizzledMethod =class_getInstanceMethod(self, swizzledSelector);method_exchangeImplementations(originalMethod, swizzledMethod);}});
Summary:
FD is a perfect encapsulated library. In fact, I liked it from the very beginning. The author is Baidu's sunnyxy. On the other hand, runtime in iOS is still a very powerful thing, most third-party libraries are just some convenient optimizations made by the Basic objc runtime, but an excellent third-party library requires continuous improvement by the author and the joint efforts of everyone.