Optimize the UITableViewCell height calculation of those things

Source: Internet
Author: User

First declare: This article is reproduced

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 is a more familiar view, its delegate and data source callbacks do not know how many times to write, 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:

1 self.tableView.rowHeight = 88;

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. The default value of the RowHeight property is 44, so an empty UITableView is displayed in 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, the RowHeight setting will not be valid. Therefore, this method is suitable for UITableView with multiple cell heights.

Estimatedrowheight

This property appears in IOS 7, and the document describes its role:

1 If the table contains variable height rows, it might be expensive to calculate all their heights when the table loads. Using estimation allows you to 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 the specified contentsize after it can be based on their 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 /c0> on the cell. Similar to the above rowHeight, there are two ways to set this estimate height:

12345 self.tableView.estimatedRowHeight = 88;// or- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {    // return xxx}

The difference is that even in the face of a different kind of cell, we can still use a simple Estimatedrowheight property assignment, as long as the overall estimate is close to, for example, about half the cell height is 44, and half the cell height is 88, then you can estimate a 66, basic Meet expectations.

When you're done with the basic use of the estimated height, you can begin to spit out the groove:

    1. 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".

    2. 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.

    3. 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)

IOS 8 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:

1 CGFloat height = textHeightWithFont() + imageHeight + topMargin + bottomMargin + ...;

A class method that is likely to be a cell when uitableviewdelegate is called:

123 @interface BubbleCell : UITableViewCell+ (CGFloat)heightWithEntity:(id)entity;@end

A variety of magic margin coupled with the screen width.

AutoLayout times a lot better, provided the-systemlayoutsizefittingsize: API, in the Contentview set constraints, you can calculate the exact value, the disadvantage is that the calculation speed is not fast, and this is an example method, Need to maintain a special for the calculation of the height of the template layout cell, it also requires users to be more skilled in 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 preferredmaxlayoutwidth after it will know when the line is broken. 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 launched the concept of self-sizing cell, which is designed to allow the cell to take charge of its own height calculations, using frame layout and auto layout to enjoy:

This feature first requires IOS 8, if the minimum supported version of the system is less than 8, but also for the old version of a write set of old-fashioned high (embarrassing), but with the API to not new faces:

12 self.tableView.estimatedRowHeight = 213;self.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:

    1. This auto-high is highly bizarre when pushing to the next page or to the screen, but the version is now fixed.

    2. 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:

    1. When the height estimate is not turned on, the UITableView will have to be high for all cell calls to determine contentsize

    2. Dequeuereusablecellwithidentifier:forindexpath: The height calculation is called more than the version without "Forindexpath"

    3. IOS 7 Calculates a height with a "cache" mechanism that does not repeat calculations, and IOS8 calculates the cell height whenever it is recalculated.

IOS 8 makes this look like a height calculation, and from WWDC it can be explained that the cell is thought to be able to change heights at any time (such as adjusting the dynamic font size from the settings), so the height is recalculated every time you swipe out.

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 of Uitableview+fdtemplatelayoutcell is undoubtedly one of the best practices for solving high-level problems, with iOS8 self-sizing simple API, and iOS7 smooth sliding effects, and minimal support iOS6.

This is probably how it is used:

1234567 #import- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {    return[tableView fd_heightForCellWithIdentifier:@"identifer"cacheByIndexPath:indexPath configuration:^(id cell) {        // 配置 cell 的数据源,和 "cellForRow" 干的事一致,比如:        cell.entity = self.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 only meant to participate in height calculations and will not really show up on the screen; it is created and saved by UITableView's-dequeuecellforreuseidentifier: Method lazy, so this reuseid must already be registered to the UIT In Ableview, that is, either the prototype cell in Storyboard or the-registerclass:forcellreuseidentifier of UITableView: or-registernib: Forcellreuseidentifier: One of the registration methods.

  • Automatic height calculation based on AutoLayout constraints

    Using the api:-systemlayoutsizefittingsize provided by the system in IOS6:

  • 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, when invoking a method such as-reloaddata,-deleterowsatindexpaths:withrowanimation: such as any one that triggers the UITableView refresh mechanism. Existing high caches 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 content and a different cell height, swipe from head to tail, swipe from tail to head, IOS 8 system, IPhone6, use time Profiler to monitor how long it takes to calculate 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 the problem of-preferredmaxlayoutwidth, adding a width constraint to contentview with the same width as the table view before calculating the height, forcing the control inside the Contentview to know its parent view width, and then to calculate their own outside the constraints of the width, to 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 UITableView, but this implementation is very elegant and may destroy the external delegate structure, but fortunately we have runloop this tool, understand its operating mechanism, can use very simple code to implement the above function.

Idle Runloopmode

The concept of Runloopmode is described in the once-runloop-line sharing (video-stamped).

When the user is sliding the Uiscrollview, Runloop switches to Uitrackingrunloopmode to accept the swipe gesture and handle the slide event (including deceleration and spring effects), at which point the other Mode (except Nsrunloopcommonmodes The events under this combination mode are all paused 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 (same as 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:

    1. Runloop start

    2. Runloop about to process the timer

    3. Runloop About to process source

    4. Runloop is about to enter sleep state

    5. Runloop is about to wake up from hibernation

    6. 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:

    1. Runloop in "Idle" state mode

    2. 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 CFRunLoopRef runLoop = CFRunLoopGetCurrent();CFStringRef runLoopMode = kCFRunLoopDefaultMode;CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 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:

123456789101112131415 NSMutableArray *mutableIndexPathsToBePrecached = self.fd_allIndexPathsToBePrecached.mutableCopy;CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {    if (mutableIndexPathsToBePrecached.count == 0) {        CFRunLoopRemoveObserver(runLoop, observer, runLoopMode);        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 pod search 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

Original link: Optimize the UITableViewCell height calculation of those things

Optimize the UITableViewCell height calculation of those things

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.