AFAIK, the reason that cause UICollectionViewLayout restart layout process are:
UICollectionView's frame change
UICollectionView reload data
UICollectionView insert/delete items
UICollectionView's bound change (if shouldInvalidateLayoutForBoundsChange return YES)
In my custom layout implementation, I want to change the UICollectionViewLayoutAttributes of certain items as the bound changes (stick header to the top like UITableView, animate item in/out .etc).
Problem is that for whatever reason it is, the UICollectionView always call its layout object's prepareLayout method (which contains heavy layout computation) and that make jerky scrolling.
I'm going for the solution that opt out the layout computation if the reason is UICollectionView's bound change.
How to check what reason the prepareLayout call?
I set a flag in shouldInvalidateLayoutForBoundsChange: so I know not to recalculate everything when that happens (on every frame...).
You can set another flag when prepareForAnimatedBoundsChange: is called, which happens when new items are inserted or when the bounds of the collection view change. You can then know if the bounds were changed because of scrolling or because of resize thanks to the flag you set in shouldInvalidateLayoutForBoundsChange:.
However, you should not have to worry too much of why you have to prepare the layout, since you should recalculate it fully in most cases anyways. In my project the only case where I do something differently in prepareLayout is when the invalidation was caused by scrolling.
Related
I have a UICollectionView with a few different types of cells and am using Auto Layout to use self-sizing cells. To accomplish this, I am setting estimatedItemSize on my UICollectionViewFlowLayout and ensuring the constraints in each of the cells is correct (so I am not using the preferredLayoutAttributesFitting method). The first time the collection view loads, everything looks correct and I get no errors in the logs. Debug View Hierarchy doesn't show any issues either.
In one of the sections, there is a cell that has radio buttons and can cause the height of the content to change based on which radio button is selected. In order to resize the cell after tapping a button, I call collectionView.collectionViewLayout.invalidateLayout().
When this occurs, I get about 3000 lines of logs in the console complaining about broken constraints. It is seemingly complaining about every single constraint present in the cell that was adjusted. However, everything on the screen seems to look exactly how I would want it afterwards, and opening the Debug View Hierarchy doesn't show any issues.
Is this just a product of calling invalidateLayout when Auto Layout is involved? One of the constraints it is complaining about is the the height from the estimatedItemSize, so maybe that is what's causing all of this? Maybe it tries to lay out the view using the estimatedItemSize first and then realizes it should be treated as self-sizing?
Calling reloadData instead seems to work just fine, except it leads to other undesirable behavior and shouldn't need to be used as the underlying data hasn't changed.
I am trying to do something like loading up different type of cells with custom height in a uitableview. The tableview cells are subclassed and consists of labels with the respective constraints. Each cell is having a dynamic height.
Now even before my table reloads the data, I am calculating the height that is required for the resizing of the cells and caching it in my model class so that I dont have to calculate the height when the data is rendered on the device.
To calculate height i did use the tutorial from Ray Wenderlich and I am having the right set of heights applies to the objects.
Now the problem comes. Whenever I am dequeueing the cells there is a
kind of a small jerk that gives me an indication that my cell is
dequeued while scrolling.
How can i make these movement smooth so that there is no jerk while scrolling the view ?
The height is getting assigned in and does get the value as per the current type of data getting loaded.
estimatedRowForIndexPath
Also I am calling layoutIfNeeded from my cellForAtindexPath
Suggestions are most welcome.
It's very hard to say without seeing your code in cellForRowAtIndexPath, and without seeing your cells and their respective code. Here are some general questions I would investigate:
What is the content of the cells and how complex is the view hierarchy in the cell?
Even though you are supplying the correct estimated height, an autolayout pass still needs to happen, and a complex view hierarchy will take time to resolve
Does the cell contain images?
Images that need to be decompressed from a file (UIImage imageNamed:) can be intensive and cause scrolling issues, check images are not bigger than they need to be. If needed, bump this work onto a background thread.
Are you calling a complex method to configure the cell for display in cellForRowAtIndexPath?
Look at the work actually being done in cellForRowAtIndexPath, is there a complex method being triggered in you cell subclass or view model?
Are you adding and removing views to the cell view hierarchy in cellForRowAtIndexPath?
If views are being added, removed, created, inflated from a xib, constrained etc during the cell config, this could slow things down. Try to do only what is strictly needed. Check if there is any code being run internally in the cell subclass during cellForRowAtIndexPath that could be moved to cells initWith... or awakeFromNib methods (ie code that could just run once when the cell is created, rather than every time the cell is displayed)
Also run the Instruments time profiler, see if that offers any more clues
To implement a rather intricate design of a screen in an iOS app, I have a UITableView nested inside of a UIScrollView.
To keep the logic simple, I implemented a method on the UITableView that calculates its entire height, and i use the result of that method and set a constraint on the nested table view, so that the scrolling logic can be solely on the UIScrollView to deal with. (I forward methods such as scrollRectToVisible from the UITableView to the UIScrollView)
While this works great with small data sets, I have recently discovered the the reuse capabilities of the UITableView are not used, because the framework believes the entire UITableView to be visible when I set that height constraint. A simple log method in the cellForRowAtIndexPath method shows all cells get calculated at once.
My question is, is there anything I can do where I would be able to tell the nested UITableView how much of it is actually visible on screen, and to only compute those visible cells?
I basically need to override whatever part of UITableView that is responsible for calculating what cells should be visible on screen.
The table view will think of itself as filling its whole frame with cells. If you limit the height it will limit the cell count visible. Are you using the deque with reuse identifier method (if not see below)
How can I recycle UITableViewCell objects created from a XIB?
When you invalidate layout on a UICollectionViewFlowLayout it creates a bunch of new layout attributes for each of your cells; it doesn't tell your cells to redraw however, which causes distortions in any Layer drawings.
I don't want to tell my collection to reload its data because this removes any nice transitions you have between flow attributes : I have a grid layout transitioning into a coverflow for example.
I need a way for the UICollectionViewController class to tell the cells to call their [setNeedsDisplay] method after being given the layout attributes.
When your cells change size, it's up to them how to handle that (stretching vs redrawing). This is controlled by the UIView contentMode property; try setting it to UIViewContentModeRedraw to cause resizing to invalidate your views contents like setNeedsDisplay: would.
If you're using CALayers directly as sub-layers of your cell, you can set their needsDisplayOnBoundsChange to YES in order to get the same effect.
Custom UITableViewCell's subviews added in code using auto layout works (verified). However the whole point of doing this was to not have to calculate the height of each tableview cell and the delegate method heightForRowAtIndexPath expects a height while drawing the tableview.
How can I figure out this height based on content using the auto-layout (visual format language based addition in code already added and working) and return it to this heightForRowAtIndexPath?
Also I'm I can't really do this on a background thread (or can I?) and therefore if I have a UITableView using this backed by a list of say 1000 records, this will take forever, is that right?
Autolayout in this case just means that you don't need to calculate the frame sizes of your subviews within each cell. It's got nothing to do with the heightForRowAtIndexPath method - this is used by the table view to define the cell's frame, which will then inform the layout of the subviews.
Using Autolayout to determine the heights would likely be pretty slow, and you can't do it on a background thread. If you have 1000 rows then I'd consider a hierarchical structure instead of a single table as that will be pretty tedious to scroll through. You could also consider calculating the heights at the point of setting or creating the data.
You may be able to set up an offscreen view using your constraints, populate it with your data for each item, then record and cache the height. However you'd have to do this at the data end rather than in the height method, as it would be far too slow.