UICollectionView cell layout is out of the collection view's bounds - ios

I have a UICollectionView which lays its cells out using Flow Layout. It is vertically scrolling.
It fills a 320x480 screen and displays a custom UICollectionViewCell which is always 96*96 - the size is set in Interface Builder and the delegate does not implement methods to set the item size on a per item basis.
There are insets on the left and right which are 10 px each and the minimum spacing is set to 6px.
What happens therefore is 3 cells per horizontal line.
<-10-><----96----><-6-><----96----><-6-><----96----><-10-> = 320.
The problem that I am having is that occasionally it lays 4 cells on one line!
The 4th cell is mostly off the screen. It then lays only 2 cells out on the following line to compensate. The whole point of flow layout is that it is a line-breaking layout which should not put anything off-screen!
I have attached a picture:
Please note that on the second row there is a fourth item mostly off the screen.
I really have no idea what could be causing it. The cells are dynamically filled with data but their size is constant. The cells on all other rows are fitting just fine so there is no reason why on some rows this should happen.
This error occurs at different places each time the collection view is updated. ie. it might happen at row 2 then once an update happens it might happen at row 10 and so on.
The collection view is being updated by a timer. The timer essentially calls a function which processes some data, and then using dispatch_async (onto the main queue) an array containing the backing data is updated and [collectionView reloadData] is called. So I haven't updated the collection view from any thread apart from the main thread. The backing array is only updated on the main queue and in that function; not from anywhere else.
Please could someone give me some suggestions as to what might be going wrong.
Thanks

I have fixed it by adding [_collectionViewFlowLayout invalidateLayout] in scrollViewDidScroll: delegate methods.
https://github.com/f33chobits/FSCalendar/issues/21

Related

UITableView does not handle contentOffset correctly when scrolling/paging

I have a table view over very large amount of data.
For performance reasons, it can't be loaded all at once.
More, sometimes random place of array should be loaded, so incremental pagination is not good option.
The current solution to these requirements is sliding window over data array. As user scrolls, I add cells from one end and remove from opposite end. I use scroll position (by looking at what cells are going onscreen) to determine whether it's time to load new data.
Usually, when you call tableView.deleteRows(at:with:) and remove cells from the beginning of table, tableView adjusts its contentOffset property so user still see same cells as before operation.
However, when tableView is decelerating after scrolling, its contentOffset is not adjusted on updates, and this causes loading new pages over and over until deceleration is completed. Then, on first update after deceleration, contentOffset is fixed by tableView and loading stops.
Same thing occurs when scrolling back and adding values at the beginning of table with tableView.insertRows(at:with:).
How can I make UITableView adjust its contentOffset properly?
OR are there other ways to overcome this bug -- keeping the ability to load arbitrary piece in the middle of data array and scroll from it?
I made a tiny project illustrating the bug:
https://github.com/wsb9/TableViewExample
From your sample project, I can understand is you are trying to implement infinite scroll through the window content concept so that you can always have fixed number of rows (index paths ,lets say 100) so that when window scrolls down/up - table view remove indexPaths from top/bottom accordingly.
And even though you have more data source item you can always have tableView of indexPaths 100
Basically you are dealing with two problem here:
ContentOffset
Dynamic height
Let's assume we have height is fixed (44) and table is not inverted.
To implement Window for infinite scrolling you have to do following:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let bottom: CGFloat = scrollView.contentSize.height - scrollView.frame.size.height
let buffer: CGFloat = 3 * 44
let scrollPosition = scrollView.contentOffset.y
if (scrollPosition > bottom - buffer) {
dataSource.expose(dataSource.exposedRange.shift(by: 25))
self.tableView.contentOffset.y -= self.dataSource.deltaHeightToRemove
}
}
Decide how much height buffer you need to keep when scroll goes down. This height buffer is the height after which you decides to insert some more item (25) into the datasource.
At this point you now have to remove items from the top
As you remove item from top you are basically telling scrollView to reduce it's content-offset by same height.
In this way total content size will be fixed every time
Hope it will help.
EDIT:-
Here is the modified code which actually does infinite scroll at bottom of
table view with dynamic cell height. This doesn't increase the rows count more than 100. but still loads data in sliding window.
link
From your sample project, I can understand the following,
One thing is you want to increase performance of your table view by only loading few number of cells at a time
Your second concern is sometimes you want to load table view with data that randomly placed in data source array
I checked your code and you have implemented your sliding-window over data-source model very interestingly. The problem caused because of you had been trying to make tableview efficiently by removing and readding cells.
Actually the Dequeuing a cell should be reusing a cell already in memory. Please take a look at the apple documentation,
For performance reasons, a table view’s data source should generally
reuse UITableViewCell objects when it assigns cells to rows in its
tableView(_:cellForRowAt:) method. A table view maintains a queue or
list of UITableViewCell objects that the data source has marked for
reuse. Call this method from your data source object when asked to
provide a new cell for the table view. This method dequeues an
existing cell if one is available or creates a new one using the class
or nib file you previously registered. If no cell is available for
reuse and you did not register a class or nib file, this method
returns nil.
The good news is your sliding-window over data-source model is working perfectly once I removed your row delete and readd mechanism. Here is your working code,
https://drive.google.com/file/d/0B2y_JJbzjRA6dDR3QzRMUzExSGs/view?usp=sharing

awakeFromNib() called twice in UICollectionViewCell subclass at scroll start

I have custom UICollectionViewCell in xib. When ViewController is loaded - awakeFromNib() in cell subclass called 6 times (the screen initially placed 6 cells, ok), BUT when i start to scroll, awakeFromNib() is called six times more, causing lag. But after this "re-load" scrolling begins to work smoothly. This happens only the first time at scroll start. dequeueReusableCell, obviously, is used.With UITableView instead collection the situation is similar.
Feel like the next 6 cells are loaded from the xib again (in the collection of just 100+ cells), ignoring reuse. What could be the problem?
For clarity:
UIColectionView with Vertical scroll direction, two cells in line. When ViewController loaded i see 6 cells on screen and 6 awakeFromNib calls in console.
I start scroll down to 7-8 cells, before their appearance on the screen i see lag and 2 calls of awakeFromNib method.
Similarly for the following two pairs of cells.
After this scroll begins work fine, until cells 99-100+, and awakeFromNib is never invoked.
Added:
OK, here's what I found out:
I have a subView inside a collectionView (header like in UITableView), and, accordingly, collectionViewLayout set sectionInsets top = height of header. If I remove the header, and make the top insets, for example: -180 (about excluding the height of NavigationBar, so the screen initially fit 12 cells and the scroll lag is gone.
Ie, the system needs to load from the xib at all the cells that will be one-time visible on the screen and reuse them in future. (But why not one time load xib and reuse it immediately for all other initially cells?)
And now the question - how to fix it?
Added.2
Working solution - add header in viewDidAppear method of ViewController, but this... hm.. not good.
This is actually a known side effect (or purposeful design?) of UICollectionView & UITableView. Here's a related question (talking about NSTableView, but the concepts/issues are the same):
awakeFromNib method called multiple times
Your collection view initially displays six cells to start with, but it sounds like iOS is allocating six more cells to allow for scrolling left & right. So there are at least 12 (reuseable) cells in memory.
If you're doing a lot of work in awakeFromNib (which is what's contributing to the lag), you might want to try to determine how to optimize things.
You can also find some additional helpful hints in this related question:
UITableViewCell - Best place to set up the cell

Dynamic cell height issue with UITableViewCell autolayout jerk while scrolling

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

Custom UITableViewCell's subviews using auto layout - no xibs, all in code

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.

Issues regarding dynamic resizing of label and row heights (iOS)

Context:
Building an app that populates a table that takes in data from a asyc json dump.
The cells are of a custom class (I defined). The main label in the cell can be very long.
It is "placed" in storyboard within a prototype cell but customized via code (pretty standard stuff).
Labels are resized in cellForRowAtIndexPath and rows are resized via heightForRowAtIndexPath -- rows are resized by forcing a call to cellForRowAtIndex like Massimo's answer here
So per the question at hand - I've noticed some interesting (bad) things that happen.
First issue: When the table loads, the rows and labels are dynamically resized correctly! Great! However, when I scroll down and then scroll back up, the label heights will be incorrect -- (for example) the first row was correct at loading. Then when I scroll down and then scroll back up to see it again, it will be truncated. Specifically, the row size will be fine but the label height will change and become truncated to 2 lines only. Wondering if this is because I did both storyboard and coding to customize the cell. Anybody see this before?
Second issue: When I scroll down, while the rows are sized correctly (large), the labels are short (truncated.) Wondering if it's some reverse of the above "potential answer".
"potential answer" is that the rows are all calculated and stored "up front" so that scrolling down/then back up doesn't affect it. However, when cells go "out of view" and are dequeued then when they re-viewed (scroll down/then back up) it will rely on the storyboard.(inappropriately?)
All three of your issues are symptomatic of returning the wrong height in heightForRowAtIndexPath. In my data model classes I have a calculateHeight method that I call in heightForRowAtIndexPath. The model also caches the answer so it doesn't have to recalculate it after the first call. The cell class uses the model's calculated height to layout its subviews.
"ANSWERED" by deleting the prototype cell from the storyboard and making them fully in code, the issue went away. The fundamental workings are still not understood (ie. the interactions between storyboard vs. code when cells are put queued and then viewed again)

Resources