UICollectionView Performance - _updateVisibleCellsNow

I am working on a custom UICollectionViewLayout that displays cells organized by day / week / month.

It does not scroll smoothly, and it seems that the delay is caused by the fact that [UICollectionView _updateVisibleCellsNow] is called in every rendering cycle.

Performance is OK for <30 items, but about 100 or more, it's terribly slow. Is this a limitation of UICollectionView and custom layouts, or am I not providing enough information to work properly?

Source here: https://github.com/oskarhagberg/calendarcollection

Layout: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarWeekLayout.m

Data Source and Delegate: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarView.m

Time Profile: Time profile - Custom layout

Update

Maybe it's useless? Some testing with a simple UICollectionViewController with a UICollectionViewFlowLayout , which is given approximately the same number of cells / screen, leads to a similar time profile.

Time profile - Standard flow layout

I feel that it should be able to process ~ 100 simple opaque cells at a time without jitter. Am I mistaken?

+50
ios objective-c uicollectionview
May 2 '13 at 11:21
source share
11 answers

Also remember to try to rasterize the cell layer:

 cell.layer.shouldRasterize = YES; cell.layer.rasterizationScale = [UIScreen mainScreen].scale; 

I had 10 fps without this, and an arrow of 55fps with! I am not very familiar with the GPU and layout model, so for me this is not entirely clear, but basically it smooths the rendering of all the spyware in only one bitmap (instead of one bitmap per view?). In any case, I do not know if he has any disadvantages, but it is much faster!

+89
Jun 27 '13 at 8:08
source share

I have done significant research on the performance of UICollectionView. The conclusion is simple. Performance is poor for a large number of cells.







EDIT: Sorry, just re-read the message, the number of cells you should have in order (see the rest of my comment), so there may also be a problem with cell complexity.

If your design supports it, check:

  • Each cell is opaque.

  • Cell content clips are limited.

  • Cell coordinate positions do not contain fractional values ​​(for example, they are always calculated as whole pixels)

  • Try to avoid overlapping cells.

  • Try to avoid shadows.







The reason for this is actually quite simple. Many people do not understand this, but it is worth understanding: UIScrollViews do not use Core Animation to scroll. My naive belief was that they used some kind of secret scrolling animation "sauce" and simply requested periodic updates from delegates from time to time to check the status. But in fact, scroll views are objects that do not use any drawing behavior at all. All that they actually are is a class that uses a mathematical function that abstracts the placement of the UIViews coordinates that they contain, so the Views coordinates are considered relative to the abstract plane of the contentView, and not relative to the origin of the containing view. In the scroll mode, the position of the abstract scroll plane will be updated in accordance with user input (for example, scrolling) and, of course, there is a physics algorithm that gives an β€œimpulse” to the translated coordinates.

Now, if you created your own collection layout object, you could theoretically create one that 100% changes the mathematical translation used by the basic scrollview. It would be interesting, but useless, because then it would seem that the cells do not move at all when you sit down. But I raise this opportunity because it illustrates that a collection layout object working with a collection view object performs a very similar operation with a basic scrollview. For example. it simply provides the opportunity to apply an additional mathematical frame for the personnel translation of the attributes of the displayed representations, and basically it will just be a translation of the position attributes.

Only when new cells are inserted or deleted, moved, or reloaded does CoreAnimation participate at all; most often, causing:

 - (void)performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL finished))completion 

UICollectionView requests a cell layout. Attributes for each scroll frame and each visible view is laid out for each frame. UIView are rich objects optimized for more flexibility than performance. Each time it is laid out, there are a number of tests that the system checks for alpha, zIndex, subViews, clipping attributes, etc. The list is long. These checks and any resulting changes in the view are maintained for each collection view item for each frame.

To ensure good performance, all HR operations must be completed within 17 ms. [It just won’t happen with the number of cells that you have] enclosed this item in brackets because I re-read your post and I understand that I misunderstood it. With the number of cells, there should be no performance issues. I personally found with a simplified test with vanilla cells containing only one cached image, the limit tested on iPad 3 is about 784 screen cells before performance starts to drop below 50 frames per second.

In practice, you will need less than that.

Personally, I use my own custom layout object and need better performance than the UICollectionView provides. Unfortunately, I did not miss the simple test above until I reached the development path, and I realized that there were performance problems. I'm so going to recycle the open source reverse port UICollectionView, PSTCollectionView. I think there is a workaround that can be implemented like this, just for general scrolling, each layer of a cell element is written using an operation that bypasses the layout of each cell of a UIView. This will work for me, since I have my own layout object, and I know when a layout is required, and I have a neat trick that will allow the PSTCollectionView to return to normal operation at this time. I checked the call sequence and it does not seem too complicated and does not seem absolutely impossible. But surely this is not trivial, and some additional tests must be performed before I can confirm that it will work.

+18
May 08 '13 at 9:02
source share

Some additional notes that may be helpful:

I can reproduce the problem using the stream layout with about 175 objects that are visible right away: it smoothly scrolls in the simulator, but it runs on the iPhone 5 like on black. Make sure they are opaque, etc.

enter image description here

What ultimately takes the most time seems to work with the mutable NSDictionary inside _updateVisibleCellsNow . And copying the dictionary, but also searching for items on the keys. The keys seem to be UICollectionViewItemKey, and the [UICollectionViewItemKey isEqual:] method is the most time-consuming method for everyone. The UICollectionViewItemKey contains at least the type , identifier and indexPath properties, and the indexPath property contained in it compares the [NSIndexPath isEqual:] takes longer.

From this, I assume that the hash function UICollectionViewItemKey may be missing, since isEqual: often called during dictionary lookups. Many of the elements can end with the same hash (or in the same hash bucket, not sure how NSDictionary works).

For some reason, it is faster with all elements in 1 section compared to many sections with 7 elements in each. Probably because it spends so much time on NSIndexPath isEqual, and it's faster if the string is different at first, or maybe the UICollectionViewItemKey gets the best hash.

Honestly, it is very strange that the UICollectionView makes this heavy dictionary, working every scroll frame, as mentioned before, before each frame update should be <16ms to avoid lag. I wonder if there are so many dictionary searches:

  • Really necessary for the overall work of UICollectionView
  • There, to maintain some extreme edge, which is rarely used and can be disabled for most layouts
  • Some unoptimized internal code that has not yet been fixed
  • Some mistake on our part

Hopefully in the summer we will see some improvement during WWDC, or if someone else can figure out how to fix it.

+8
May 09 '13 at 16:36
source share

The problem is not the number of cells that you display in the general view of the collection, this is the number of cells that are on the screen at the same time. Since the cell size is very small (22x22), you have 154 cells visible on the screen at the same time. Providing each of them is what slows down your interface. You can prove this by increasing the size of the cell in your storyboard and restarting the application.

Unfortunately, you cannot do much. I would recommend mitigating the problem by avoiding cropping to borders and trying not to implement drawRect: since it is slow.

+6
May 6 '13 at 13:30
source share

Thumbs up to the two answers above! Here's another thing you can try: I had big improvements in UICollectionView performance by disabling auto layout. Although you will have to add extra code to layout the cells, custom code looks much faster than an auto market.

+6
May 08 '13 at 11:00
source share

Here is Altimac's answer converted to Swift 3:

 cell.layer.shouldRasterize = true cell.layer.rasterizationScale = UIScreen.main.scale 

Also, it should be noted that this code goes in your collectionView delegate method for cellForItemAtIndexPath.

Another tip is viewing application frames per second (FPS), opening the main animation in the "Tools" section (see image).

enter image description here

+5
Nov 09 '15 at 18:36
source share

Like @Altimac, I also used this code to solve the iPad scrolling issue on the UICollectionView:

  cell.layer.shouldRasterize = YES; cell.layer.rasterizationScale = [UIScreen mainScreen].scale; 

I also had 6-10 frames per second, and now I have 57 fps

+4
Aug 20 '13 at 15:06
source share

In some cases, this is due to automatic layout in the UICollectionViewCell. Disable it (if you can live without it) and the scroll will become butter :) This is an iOS problem that they havnt solved with age.

+4
Apr 10 '15 at 11:45
source share

If you are implementing a grid layout, you can get around this using one UICollectionViewCell for each row and add a nested UIView to the cell . I actually subclassed UICollectionViewCell and UICollectionReusableView and redefined the prepareForReuse method to remove all subqueries. In collectionView:cellForItemAtIndexPath: I add to all subviews that were originally cells, setting their frame to the x coordinate used in the original implementation, but adjusting its y coordinate inside the cell . Using this method, I was still able to use some of the intricacies of the UICollectionView , such as targetContentOffsetForProposedContentOffset:withScrollingVelocity: to align the top and left sides of the cell well. I went from getting 4-6 FPS to a smooth 60 FPS .

+3
Jul 24 '14 at 18:29
source share

In addition to the answers listed (rasterization, auto-layout, etc.), you can also check other reasons that could potentially lead to poor performance.

In my case, each of my UICollectionViewCell contains a different UICollectionView (25 cells each), when each of the cells loads, I call the internal UICollectionView.reloadData (), which significantly reduces performance.

Then I put reloadData in the main UI queue, the problem disappeared:

 DispatchQueue.main.async { self.innerCollectionView.reloadData() } 

By carefully examining reasons such as this can help. Thank you :)

+2
Oct 18 '16 at 5:13
source share

I think that I quickly give my decision, because I came across a very similar question - based on images of UICollectionView.

In the project that I was working on, I collected images over the network, cached it locally on the device, and then reloaded the cached image while scrolling.

My mistake was that I did not load cached images into the background stream.

As soon as I put my [UIImage imageWithContentsOfFile:imageLocation]; into the background thread (and then applied it to my imageView through my main thread), my FPS and scroll were much better.

If you have not tried it, be sure to let it go.

0
Jan 24 '17 at 10:30
source share



All Articles