I've implemented sticky section headers for a collection view, using http://blog.radi.ws/post/32905838158/sticky-headers-for-uicollectionview-using as a jumping off point, and they work.
But I'm seeing a very weird crash.
When I push an editor detail view, change the item's name such that it moves from one section to another ( think, changing the last name of a person in a contacts list which is grouped by first letter of last name), and then pop back to the collection view it crashes with a complaint
UICollectionView received layout attributes for a cell with an index path that does not exist
Here's the flow of execution:
In the editor detail view, I change the item's name such that it will move from section X to section Y.
The item's model emits a "name changed" notification.
The root view controller which owns the collection view catches that "name changed" notification, rebuilds its internal indexes, and then calls -reloadData on the collection view. This is all good.
I hit the Back button in the UI, and the following flow happens ( confirmed via debugger and caveman NSlog calls )
numberOfSectionsInCollectionView: is called, and my code returns correct number of sections
collectionView:numberOfItemsInSection: is called for each section, and correct number of items is returned
my custom flow layout's -layoutAttributesForElementsInRect: is called. The first thing I do in there is call [super layoutAttributesForElementsInRect] to get a baseline layout.
Logging the the inherited baseline layout I see attributes for the previous arrangement of cells, not the current one. E.g., the layout is for the arrangement before the edits I just made. So, incorrect sections and or cells in incorrect sections.
Now here's what blows my mind.
If I comment out the entire implementation of -layoutAttributesForElementsInRect, it still crashes. But, if I comment out:
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
Then, it works correctly.
This tells me that the collection view, or something in flow layout is caching results, but only if the flow layout shouldInvalidateLayoutForBoundsChange
Note, if I just use a vanilla UICollectionViewFlowLayout everything works fine.
TLDR
Custom UICollectionViewFlowLayout, if -shouldInvalidateLayoutForBoundsChange returns YES, gets out-of-date layout attributes from [super layoutAttributesForElementsInRect]
Any ideas?
I solved this by removing the shouldInvalidateLayoutForBoundsChange: override, and instead implementing scrollViewDidScroll: on the collection view's delegate to invalidate layout:
override func scrollViewDidScroll(scrollView: UIScrollView) {
collectionView?.collectionViewLayout.invalidateLayout()
}
This keeps the sticky headers but stops the crashing.
Related
Okay the situation goes as follows:
I have a collection view where in cellForRow I am using dequeueReusableCell to reuse the cells. On each cell I have a custom UIView object that is added as a subview.
Now, under a certain circumstance I must re-layout the collection view entirely. When this happens
Clear all item from data model
Call deleteItems for all visible cells' index paths
Call reloadData
At this point the collection view is empty and there are no cells displayed.
Now if I update my model again with data and reload the collection view - In cellForRow dequeueReusableCell returns reused cells/the added UIView as explained above is there!/- it does not initialize new cell objects even though the collection view was empty before the current update. I am not sure if this is the expected behaviour or I have some other problem in my code, however my question is - how can get to a point where I reset all the content on the collection view and dequeueReusableCell returns a newly initialized cell object.
I have learnt this the hard way! Save yourself some time Petar by knowing this that In any collection view/tableview, anytime there is any change of constraint or any UI element in its layout structure, you should ALWAYS, and ALWAYS make another prototype cell. It is the ONLY correct way, so don't think you ll be putting some more time in making another cell.
As I read your comment where you said you ll initiliaze another view based on that one condition where you want to reset everything. Just have another cell prototype with that another view and dequeue that now.
Hope it solves your problem
I have a problem with my collectionView. This is what I want to do and what I have at the moment:
1 collectionView
1 datasource
2 layout
2 prototyp cells
I got 2 buttons to switch between layout and prototypes cells
Can someone tell me how many times et when I have to use these methods (constructor? method when I click on a button ? subviewLayout method ?)
collectionViewTransactions.CollectionViewLayout = new CollectionViewLayoutGrid();
collectionViewTransactions.CollectionViewLayout = new CollectionViewLayoutList();
collectionViewTransactions.Source = new TransactionCollectionViewSource();
collectionViewTransactions.ReloadData();
collectionViewTransactions.CollectionViewLayout.InvalidateLayout();
Because I got 9 items and the first time, it displays only 4 (which appears on screen), How can also display all 9 instead of only which are display on screen ?
Please can someone help me, thank a lot !
Be careful not to confuse the layout object’s invalidateLayout method with the collection view’s reloadData method. Calling the invalidateLayout method does not necessarily cause the collection view to throw out its existing cells and subviews. Rather, it forces the layout object to recompute all of its layout attributes as is necessary when moving and adding or deleting items. If data within the data source has changed, the reloadData method is appropriate. Regardless of how you initiate a layout update, the actual layout process is the same.
I have a collection view with a datasource and a layout class. The class is linked to the collection view in the Attributes Inspector.
By tapping buttons I need to retrieve data for the collection and this can mean different numbers of section/items. Therefore I need to restructure the layout each time. However this never seems to occur.
At the moment I have the following in the Success function for the data retrieval request.
listingsView.reloadData()
listingsView.collectionViewLayout.invalidateLayout()
listingsView.collectionViewLayout.prepareLayout()
I'm not sure that the prepareLayout is needed but tried it anyway. The datasource updates fine but the prepareLayout is never triggered by any of the lines.
Do I need some special settings or a different location for the invalidateLayout request?
Thanks.
Sorry - ignore this. Had a Boolean variable on layout that wasn't getting altered due to a forced return.
Just listingsView.collectionViewLayout.invalidateLayout() works fine.
This is a very rare occurring bug from hell,
I have an infinite scroll controller that displays products, 2 in each row. Rarely, something affects the controller and causes items to vanish, when I tap the empty area where the item should be, it works as expected and directs the user to the item details controller. When I back out back to the list, sometimes the cell shows its content, and others get hidden.
Sometimes it just a couple of items missing, sometimes there are so many missing items that makes the list appear empty, like only 1 or 2 cells are visible per screen height.
An even stranger situation is, when I scroll really fast to the end and stretch the screen really fast out of the visible area, and there are no more items to load, the visible items can jump from left to right.
Please see these two videos.
https://www.dropbox.com/s/jibflcouz1ena8n/missingProductImages.mov?dl=0
https://www.dropbox.com/s/uz13fzorypnp38t/again.mov?dl=0
I could send code but I didn't want to clutter this place with full length code, let me know if you want to see a specific section of the code please. Maybe someone could have an idea of what might be going on by looking at the vids.
What's probably going on here is a state problem with your collection view cells. The code assigning model values to the cell's views would be of use here. But absent any actual information, the first thing to review would be the cell's prepareForReuse implementation:
Does it call super?
Does it clear out all current values?
Does it cancel any pending asynchronous operations?
Are fetches and cancellations correctly matched?
Next, check if there's any essential configuration in the cell's init/initWithCoder methods -- those are only called on first creation, not on reuse.
Those are the normal pitfalls of UICollectionView cell handling. If they don't suggest the problem, please post the cell code.
It looks like your cells are not being reused correctly.
Can you check that you have set the same reuseIdentifier for your cell in Interface Builder that you are assigning in your code?
Update: Attached image to show where to set the identifier in the storyboard/xib
Update: added layout solution
Another problem could be due to the layout bounds of your collectionViewCell. When you load your cells they bounds are not calculated until they have been added to your collectionView and rendered. This can cause the elements in your cell to layout with the wrong values. This happens commonly with async image frameworks as they cache a resized version of the image for performance. When you cell loads, the cached image is loaded at the wrong the wrong size.
Add the following code to your collectionViewCell:
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
self.contentView.frame = bounds;
[self setHighlighted:NO];
}
I'm trying to enable VoiceOver accessibility on my collection view which behaves as follows:
Upon selection of an item, it will remove the rest of the items in the section, leaving only the item that was selected.
When I select the item again, it will re-populate the section with the items that was previously removed.
For step 1, voiceover reads out the item just fine, and it'll be the focus after the layout change.
However, for step 2, things get a bit weird. It'll start reading out the item, and just before it finishes, the focus jumps to another item, and begins reading that one instead.
From Apple's documentation about collection views, it automatically posts a layout change notification when a layout occurs. But, from the looks of it, it's passing on some random element to the first focus element after the layout change.
When a collection view changes its onscreen layout, it posts the UIAccessibilityLayoutChangedNotification notification.
Is there anyway to override this automatic layout changed notification, so that I can pass in the correct focus element?
I found the reason for what was happening.
The reason an element was randomly focusing was because I was calling reloadData on the collectionview in the performBatchUpdate's completion block.
Removing the reloadData solved this problem. However, because I'm not calling reloadData, I had to call reloadItemsAtIndexPaths on specific cells, which is fine in my case.