This article reprinted to http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/
I'm the preface .
This article is a summary of what I and our team have recently UITableViewCell using autolayout automatic height calculation and uitableview sliding optimization.
We are also maintaining an open source extension, UITableView+FDTemplateLayoutCell
so that the height of computing this thing has never been easier, but also by a lot of stars support, GitHub link please poke me
In this summary you can read:
- Mechanism of UITableView height calculation and estimation
- Differences in height calculations for different iOS systems
- IOS8 self-sizing Cell
- Uitableview+fdtemplatelayoutcell How to solve the height problem with a sentence
- The skill of using Runloop in Uitableview+fdtemplatelayoutcell
UITableViewCell Height Calculation rowheight
UITableView
We are familiar with the view, its delegate and data source callback do not know how many times, but also encountered the UITableViewCell height calculation. UITableView queries the cell height in two ways.
One is for cases where all cells have a fixed height, by:
The above code specifies that all cells are 88-height uitableview, and it is strongly recommended to use this rather than the following method to guarantee unnecessary height calculations and calls for high-demand tables. rowHeight
the default value of the property is 44, so an empty uitableview appears that way.
Another way is to implement Uitableviewdelegate:
123 |
-(cgfloat) TableView: (uitableview *) TableView Heightforrowatindexpath: (Nsindexpath *) Indexpath { //Return XXX} |
It is important to note that after implementing this method, rowHeight
the settings will not be valid. Therefore, this method is suitable for UITableView with multiple cell heights.
Estimatedrowheight
This attribute is iOS7, and the document describes its role:
If The table contains variable height rows, it might is expensive to calculate all their heights when the table loads. Using estimation allows you-defer some of the cost of geometry calculation from load time to scrolling time.
Well, that sounds pretty plausible. We know that UITableView is a uiscrollview, just like the usual use of Uiscrollview, loading when specified contentSize
after it can be based on their own bounds, Contentinset, Contentoffset Properties together determine whether you can slide and the length of the scroll bar. UITableView at first did not know how much they would be filled, so ask the number of data source and create the cell, while asking delegate these cells should show the height, which caused it to load the time wasted redundant calculations on the outside of the screen /c1> on the cell. rowHeight
similar to the above, there are two ways of setting this estimate height:
12345 |
Self. TableView; or-(cgfloat) TableView: (uitableview *) TableView Estimatedheightforrowatindexpath: (//Return XXX} |
The difference is that even with a different kind of cell, we can still use a simple estimatedRowHeight
property assignment, as long as the overall estimate is close enough, for example, about half the cell height is 44, and half the cell height is 88, then you can estimate a 66, which is basically in line with the expected 。
When you're done with the basic use of the estimated height, you can begin to spit out the groove:
- After setting the estimated height, the contentsize.height is calculated based on the "cell estimate X Cell count", which causes the scrollbar to be in an unstable state, and the contentsize will be replaced by the actual height of the scroll from the estimated height, and the visible scroll bar suddenly changes even "Jump".
- If you have a poorly designed drop-down refresh or pull-up load control, or if you KVO the Contentsize or Contentoffset property, it is possible to bounce the table as it slides.
- Estimating the height of the design is good, so that loading speed faster, then what to violate the smoothness of the slide, the user may have to enter the page when more than fraction second load time feel little, but the real-time calculation of sliding the height of the lag is obviously able to experience, the individual think it is not as good as the beginning (iOS8 more excessive, Even if they are all good, they will draw the edge of the calculation)
IOS8 self-sizing Cell
Cell with dynamic height content has always been a headache, such as cell chat bubbles, the frame layout era is usually reversed with data content height:
A class method that is likely to be a cell when uitableviewdelegate is called:
123 |
uitableviewcell+ (cgfloat) heightwithentity: (ID) entity; @end |
A variety of magic margin coupled with the screen width.
AutoLayout times a lot better, provided -systemLayoutSizeFittingSize:
the API, in the Contentview set constraints, you can calculate the exact value; The disadvantage is that the calculation speed is certainly not hand-fast, and this is an example method, need to maintain a special for the calculation of the height of the birth template layout cell
, It also requires users to be more familiar with the constraints set, to ensure that the contentview inside and out of all directions are constrained support, set unreasonable if the height of the calculation is 0.
Here also has to mention a UILabel egg pain problem, when the number of UILabel rows is greater than 0 o'clock, it is necessary to specify when preferredMaxLayoutWidth
it will not know when to fold the line. This is a "chicken with eggs and eggs," the problem, because UILabel need to know the width of Superview to break the line, and the width of superview depends on the sum of the child view width to determine. This problem seems to be solved automatically by iOS8 (but we found the solution)
Back to the point, IOS8 WWDC self-sizing cell
's concept was designed to allow the cell to take charge of its own height calculations, using frame layout and auto layout to enjoy:
This feature is first required to be iOS8, if the minimum supported system version is less than 8, but also for the old version of a single write set of old-fashioned high (embarrassing), but the API to not new faces:
12 |
Self. TableView213;tableView. rowHeight = uitableviewautomaticdimension; |
Here again have to spit groove, automatically calculate rowHeight and estimatedrowheight exactly what is the enemy, if not with the estimate height of the setting, automatic calculation of the high is invalid--
The default value of RowHeight in the PS:IOS8 system has been set to Uitableviewautomaticdimension, so the second line of code can be omitted.
Problem:
- This auto-high is highly bizarre when pushing to the next page or to the screen, but the version is now fixed.
- To a company that allows the lowest support iOS8
The high mechanism of iOS8 convulsions
The same code slides smoothly on iOS7 and iOS8 completely different, iOS8 inexplicable wonderful cards. A large part of the reason is that the iOS8 on the high-level mechanism is very different, this is my small test:
The study found that many additional calculations have the following reasons:
- When the height estimate is not turned on, the UITableView will have to be high for all cell calls to determine contentsize
dequeueReusableCellWithIdentifier:forIndexPath:
The height calculation is called more than the version without "Forindexpath"
- IOS7 has a "cache" mechanism after calculating the height, does not repeat the calculation, and iOS8 will recalculate the cell height whenever
IOS8 the height calculation into this way, from the WWDC can also find a point to explain, cell is considered to be possible to change the height at any time (such as adjusting the dynamic font size from the settings), so each time you swipe out to recalculate the height.
Said so much, whether there is not only to eliminate the high trouble, but also to ensure smooth sliding, can support ios6+ one-stop solution?
Uitableview+fdtemplatelayoutcell
The use UITableView+FDTemplateLayoutCell
is undoubtedly one of the best practices to solve the problem of high-quality, both IOS8 self-sizing features a simple API, and can achieve iOS7 smooth sliding effect, but also maintain the minimum support iOS6.
This is probably how it is used:
1234567 |
<uitableview+fdtemplatelayoutcell.h>-(cgfloat) TableView: (UITableView *) TableView Heightforrowatindexpath: (Nsindexpath *) Indexpath { return [TableView fd_heightforcellwithidentifier:@ "Identifer" Cachebyindexpath:indexpath configuration:^ (//Configure the data source of the cell, and "Cellforrow" do the same thing, for example: cellself< C13>.feedentities[indexpath. row];}];} |
After you have written the above code, you have used the following:
- Template layout cell corresponding to each UITableViewCell Reuseid one by one
This cell is not really displayed on the screen in order to participate in the height calculation, it -dequeueCellForReuseIdentifier:
is created and saved by UITableView method lazy, so requires that the Reuseid must already be registered in UITableView, that is, either Storyboa The prototype cell in RD is either using UITableView -registerClass:forCellReuseIdentifier:
or -registerNib:forCellReuseIdentifier:
One of the registration methods.
- Automatic height calculation based on AutoLayout constraints
Using the API provided by the system in IOS6:-systemLayoutSizeFittingSize:
- A set of high caching mechanisms based on index path
The calculated height is automatically cached, so the true height calculation of each cell will only occur once when it is sliding, and the subsequent height query will hit the cache, reducing the amount of extra computation.
- Automatic cache invalidation mechanism
There is no need to worry about cache invalidation caused by changes in your data source, and when calling -reloadData
-deleteRowsAtIndexPaths:withRowAnimation:
any method that triggers the UITableView refresh mechanism, the existing height cache will be invalidated at minimal cost . If you delete a cell Indexpath [0:5], the height cache [0:0] ~ [0:4] is not affected, and all cached values after [0:5] are moved one position forward. The automatic cache invalidation mechanism has been processed separately for the UITableView 9 public APIs to ensure that there is no extra height calculation.
- Pre-cache mechanism
The pre-cache mechanism will execute at idle moments when the UITableView does not slide, calculate and cache cells that have not yet been shown to the screen, and the entire cache process is completely unaware, which makes the full list of height calculations not occur at the time of loading, and does not occur when sliding. At the same time, the loading speed and smoothness of the slide are ensured, and the following will focus on the implementation principle of this piece.
We have been designing the API for this tool for a very long time, both to ensure the power of the function, and to ensure that the interface is streamlined, and there are many functions hidden behind a line of calls.
How much impact does this caching mechanism have on sliding? In addition to the obvious perception of the naked eye, I also made a small test:
A table view with 54 contents and a height of different cells, sliding from head to tail, then sliding from tail to head, iOS8 system, IPhone6, using Time Profiler
monitoring to calculate the time spent on high functions:
Not using the cache API, unused estimates, total cost 877 MS:
Use cache API, open estimate, total cost of MS:
The accuracy of the test data regardless, from the magnitude of an order of magnitude, to tell the truth himself did not think that the gap is so large-
At the same time, the tool also solves -preferredMaxLayoutWidth
the problem, in the calculation of the height of the Contentview with a table view width of the same width constraint, forcing the Contentview inside the control to know the width of their parent view, and then to calculate their own outside the constraints of the width, Break the "chicken raw eggs and eggs," the problem, here more tricky, do not expand said. The following is an implementation that leverages the Runloop pre-cache.
Perform pre-cache tasks with Runloop idle time
Fdtemplatelayoutcell's high-level pre-cache is an optimization feature that requires the page to be idle to perform calculations, and when the user is sliding the list, it is clear that the calculation task should not affect the sliding experience.
In general, this function is to be coupled to the sliding state of the uitableview, but this implementation is very elegant and may break the external delegate structure, but fortunately we have RunLoop
this tool, understand its operating mechanism, can use very simple code to implement the above functions.
Idle Runloopmode
The concept of Runloopmode is described in the once-runloop-line sharing (video-stamped).
When the user is sliding the Uiscrollview, Runloop will switch to UITrackingRunLoopMode
accept the swipe gesture and handle the slide event (including the deceleration and spring effect), at which point the other Mode (except nsrunloopcommonmodes this combination mode) will pause all execution to ensure the priority of the sliding event, which is an important reason for the smooth running of the IOS.
When the UI is not sliding, the default mode is NSDefaultRunLoopMode
(with Kcfrunloopdefaultmode in CF) and also the "idle state Mode" defined in cf. When the user does not point, at this time there is no network IO, is in this mode.
Use Runloopobserver to find the right time.
Registering Runloopobserver can observe the current Runloop status and receive notifications when the state machine switches:
- Runloop start
- Runloop about to process the timer
- Runloop About to process source
- Runloop is about to enter sleep state
- Runloop is about to wake up from hibernation
- Runloop exit
Because the "pre-cache height" task needs to be done at the least-perceived moment, it should be met at the same time:
- Runloop in "Idle" state mode
- When this time the Runloop iteration finishes all the events and immediately sleeps
A block version of the registration function using CF can make the code more concise:
1234567 |
0, ^ (cfrunloopobserverref Observer, Cfrunloopactivity _) { //TODO here}); Cfrunloopaddobserver (RUNLOOP, Observer, Runloopmode); |
In the TODO position, you can begin the collection and distribution of tasks, and of course, don't forget to remove this observer in a timely manner.
Decomposition into multiple runloop source tasks
Assuming that the list has 20 cells and the first 5 are displayed after loading, the table view only calculates the 5 heights after the open estimate, and the remaining 15 is the "Pre-cache" task, and we do not want the 15 compute tasks to be executed synchronously in the same runloop iteration, so that the card The UI, so you should separate them into 15 runloop iterations, and then you need to manually add the source task to the Runloop (the source 0 task is initiated and processed by the app)
The Foundation layer does not provide a direct-build API to Runloopsource, but it provides an indirect, familiar, and unfamiliar API:
12345 |
(void) Performselector:(SEL) aselector onthread:(nsthread *) thr Withobject:(ID) arg Waituntildone:(BOOL) wait modes:(nsarray *) array; |
This method creates a Source 0 task, distributes it to the specified thread's Runloop, executes it under a given mode, and if the specified Runloop is dormant, wakes it up to handle the event, simply to say, "Sleep you xx, get up Hi!" ”
We then use a mutable array to load all the current index paths that need to be "pre-cached", and each Runloopobserver callback takes the first task out of the distribution:
12345678910111213141516 |
Nsmutablearray *mutableindexpathstobeprecached =Self. fd_allindexpathstobeprecached. mutablecopy; Cfrunloopobserverref observer = Cfrunloopobservercreatewithhandler (Kcfallocatordefault, kcfrunloopbeforewaiting,true, 0, ^ (Cfrunloopobserverref Observer, Cfrunloopactivity _) {if (mutableindexpathstobeprecached.count = = 0) { Cfrunloopremoveobserver (RUNLOOP, Observer, Runloopmode); CFRELEASE (Observer); //attention release, otherwise it will cause memory leak return;} nsindexpath *indexpath = mutableindexpathstobeprecached.firstObject ; [Mutableindexpathstobeprecached Removeobject:indexpath]; [self performselector: @selector (fd_precacheindexpathifneeded:) Onthread:[nsthread Mainthread] withobject:indexpath waituntildone:NO Modes:@[nsdefaultrunloopmode]; |
In this way, each task is assigned to the next "idle" runloop iteration, in which a sliding event starts, Mode switches to Uitrackingrunloopmode, and all the "pre-cache" Tasks are automatically scheduled for distribution and execution, with maximum sliding smoothness.
Getting Started with Uitableview+fdtemplatelayoutcell
If you think this tool can help you, it's easy to integrate into the project.
Using Cocoapods:
1 |
Uitableview+fdtemplatelayoutcell |
The latest version of this article was 1.2, which removed the previous version of the Black Magic and added the pre-cache feature.
Welcome to use and support this tool, there are bugs please feel free to feedback oh ~
and review it. GitHub Address: Https://github.com/forkingdog/UITableView-FDTemplateLayoutCell
in
Original article, reprint please specify the original address: blog.sunnyxx.com
What do you mean to bloggers? Sina Weibo @ I'll call sunny.
Or search for a subscription number Sunnyxx or sweep below the tease dog
Optimize the UITableViewCell height calculation of those things