UICollectionView flowLayout not wrapping cells correctly (iOS)

I have a UICollectionView with FLowLayout. It will work as I expect most of the time, but from time to time one of the cells does not wrap properly. For example, a cell that should be included in the first "column" of the third row, if it actually ends in the second row, is just an empty place where it should be (see the diagram below). All that you can see in this gun cell is the left side (the rest are cut off), and the place where it should be is empty.

This does not occur sequentially; it is not always the same line. As soon as this happens, I can scroll up and then back, and the cell will be fixed. Or, when I click on a cell (which brings me to the next view by clicking), and then is discarded, I see the cell in the wrong position, and then it will move to the correct position.

Scrolling speed seems to make it easier to reproduce the problem. When I scroll slowly, I still see the cell in the wrong position from time to time, but then it will immediately move to the correct position.

The problem started when I added insert sections. Previously, I had cells that almost merged with the borders of the collection (small or without inserts), and I did not notice a problem. But this meant that the right and left views of the collection are empty. That is, it was not possible to scroll. In addition, the scroll bar was not hidden to the right.

I can solve the problem on both Simulator and iPad 3.

I assume the problem is due to the left and right sections of the insert ... But if the value is incorrect, I would expect the behavior to be consistent. I wonder if this could be a mistake with Apple? Or maybe it's related to creating an insert or something similar ...

Any solutions / workarounds appreciated.

Illustration of problem and settings




Follow-up : I used this answer below Nick for more than 6 months 12 months 2 years without problems (in case people are wondering if there are any holes in this answer - I have not found them yet). Well done Nick.

+75
ios ios6 uicollectionview uicollectionviewcell
Oct 17 '12 at 4:18
source share
10 answers

There is an error in the UICollectionViewFlowLayout in the implementation of layoutAttributesForElementsInRect, which causes it to return TWO attribute objects for a single cell in certain cases, including sectional inserts. One of the returned attribute objects is invalid (outside the boundaries of the collection view), and the other is valid. Below is a subclass of UICollectionViewFlowLayout that fixes the problem by excluding cells outside the boundaries of the collection view.

 // NDCollectionViewFlowLayout.h @interface NDCollectionViewFlowLayout : UICollectionViewFlowLayout @end // NDCollectionViewFlowLayout.m #import "NDCollectionViewFlowLayout.h" @implementation NDCollectionViewFlowLayout - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *attributes = [super layoutAttributesForElementsInRect:rect]; NSMutableArray *newAttributes = [NSMutableArray arrayWithCapacity:attributes.count]; for (UICollectionViewLayoutAttributes *attribute in attributes) { if ((attribute.frame.origin.x + attribute.frame.size.width <= self.collectionViewContentSize.width) && (attribute.frame.origin.y + attribute.frame.size.height <= self.collectionViewContentSize.height)) { [newAttributes addObject:attribute]; } } return newAttributes; } @end 

See this .

Other answers suggest returning YES from shouldInvalidateLayoutForBoundsChange, but this leads to unnecessary recalculations and does not even completely solve the problem.

My solution completely solves the problem and should not cause any problems when Apple fixes the root cause.

+91
Nov 14 '12 at 23:55
source share

I found similar problems in my iPhone application. A search in the Apple dev forum brought me this suitable solution that worked in my case and probably also in your case:

Subclass UICollectionViewFlowLayout and override shouldInvalidateLayoutForBoundsChange to return YES .

 //.h @interface MainLayout : UICollectionViewFlowLayout @end 

and

 //.m #import "MainLayout.h" @implementation MainLayout -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{ return YES; } @end 
+7
Oct 31
source share

Put this in the viewController that owns the collection view

 - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [self.collectionView.collectionViewLayout invalidateLayout]; } 
+7
Jun 03 '14 at 8:48
source share

Quick version of Nick Snyder's answer:

 class NDCollectionViewFlowLayout : UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) let contentSize = collectionViewContentSize return attributes?.filter { $0.frame.maxX <= contentSize.width && $0.frame.maxY < contentSize.height } } } 
+7
Jul 03 '15 at 12:06
source share

I had this problem, as well as for a basic gridview schema with inserts for fields. The limited debugging that I have performed so far implements - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect in my subclass of UICollectionViewFlowLayout and registers that the implementation of the superclass returns, which clearly shows the problem.

 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *attrsList = [super layoutAttributesForElementsInRect:rect]; for (UICollectionViewLayoutAttributes *attrs in attrsList) { NSLog(@"%f %f", attrs.frame.origin.x, attrs.frame.origin.y); } return attrsList; } 

By implementing - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath , I also see that it returns the wrong values ​​for itemIndexPath.item == 30, which is a factor of 10 that has more cells than that.

 - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath { UICollectionViewLayoutAttributes *attrs = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; NSLog(@"initialAttrs: %f %f atIndexPath: %d", attrs.frame.origin.x, attrs.frame.origin.y, itemIndexPath.item); return attrs; } 

With no time for more debugging, the workaround I have made so far is reducing my collection width equal to left and right. I have a header that still needs full width, so I set clipToBounds = NO in my collection view and then removed the left and right inserts on it, it seems to work. To view the title, and then stay in place, you need to implement frame offset and calibration in the layout methods that are instructed to return layoutAttributes to represent the title.

+5
Oct 17 '12 at 9:28
source share

I added an error report to Apple. What works for me is to set the bottom section of the Inset to a value smaller than the top insert.

+4
Nov 08 '12 at 13:26
source share

I ran into the same issue of changing cells on an iPhone using UICollectionViewFlowLayout , and so I was glad to find your post. I know that you have a problem on the iPad, but I am posting this because I think this is a common problem with UICollectionView . So here is what I found out.

I can confirm that sectionInset related to this issue. In addition, headerReferenceSize also affects whether a cell is disabled or not. (This makes sense, as it is necessary to calculate the origin.)

Unfortunately, even different screen sizes need to be considered. When you played with the values ​​for these two properties, I experienced that a certain configuration worked on both (3.5 "and 4"), not one, not one of the screen sizes. Usually none of them. (This also makes sense, since the boundaries of the UICollectionView are changing, so I did not experience differences between the retina and non-retina.)

I ended up setting sectionInset and headerReferenceSize depending on the screen size. I tried about 50 combinations, until I found the meaning for which the problem no longer arose, and the layout was visually acceptable. It is very difficult to find values ​​that work on both screen sizes.

So, in summary, I can just recommend you play around with the values, check them on different screen sizes, and hope that Apple fixes this problem.

+3
Oct 29 '12 at 17:50
source share

I just experienced a similar problem, but found a completely different solution.

I am using a custom implementation of UICollectionViewFlowLayout with horizontal scrolling. I also create custom frame locations for each cell.

The problem I ran into was that [super layoutAttributesForElementsInRect: rect] did not actually return all the UICollectionViewLayoutAttributes that should be displayed on the screen. When calling [self.collectionView reloadData], some of the cells will suddenly be set to hidden.

As a result, I created an NSMutableDictionary that cached all the UICollectionViewLayoutAttributes that I have seen so far, and then include any elements that I know should be displayed.

 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect]; NSMutableArray * attrs = [NSMutableArray array]; CGSize calculatedSize = [self calculatedItemSize]; [originAttrs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attr, NSUInteger idx, BOOL *stop) { NSIndexPath * idxPath = attr.indexPath; CGRect itemFrame = [self frameForItemAtIndexPath:idxPath]; if (CGRectIntersectsRect(itemFrame, rect)) { attr = [self layoutAttributesForItemAtIndexPath:idxPath]; [self.savedAttributesDict addAttribute:attr]; } }]; // We have to do this because there is a bug in the collection view where it won't correctly return all of the on screen cells. [self.savedAttributesDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSArray * cachedAttributes, BOOL *stop) { CGFloat columnX = [key floatValue]; CGFloat leftExtreme = columnX; // This is the left edge of the element (I'm using horizontal scrolling) CGFloat rightExtreme = columnX + calculatedSize.width; // This is the right edge of the element (I'm using horizontal scrolling) if (leftExtreme <= (rect.origin.x + rect.size.width) || rightExtreme >= rect.origin.x) { for (UICollectionViewLayoutAttributes * attr in cachedAttributes) { [attrs addObject:attr]; } } }]; return attrs; } 

Here is the category for NSMutableDictionary that UICollectionViewLayoutAttributes are stored correctly.

 #import "NSMutableDictionary+CDBCollectionViewAttributesCache.h" @implementation NSMutableDictionary (CDBCollectionViewAttributesCache) - (void)addAttribute:(UICollectionViewLayoutAttributes*)attribute { NSString *key = [self keyForAttribute:attribute]; if (key) { if (![self objectForKey:key]) { NSMutableArray *array = [NSMutableArray new]; [array addObject:attribute]; [self setObject:array forKey:key]; } else { __block BOOL alreadyExists = NO; NSMutableArray *array = [self objectForKey:key]; [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *existingAttr, NSUInteger idx, BOOL *stop) { if ([existingAttr.indexPath compare:attribute.indexPath] == NSOrderedSame) { alreadyExists = YES; *stop = YES; } }]; if (!alreadyExists) { [array addObject:attribute]; } } } else { DDLogError(@"%@", [CDKError errorWithMessage:[NSString stringWithFormat:@"Invalid UICollectionVeiwLayoutAttributes passed to category extension"] code:CDKErrorInvalidParams]); } } - (NSArray*)attributesForColumn:(NSUInteger)column { return [self objectForKey:[NSString stringWithFormat:@"%ld", column]]; } - (void)removeAttributesForColumn:(NSUInteger)column { [self removeObjectForKey:[NSString stringWithFormat:@"%ld", column]]; } - (NSString*)keyForAttribute:(UICollectionViewLayoutAttributes*)attribute { if (attribute) { NSInteger column = (NSInteger)attribute.frame.origin.x; return [NSString stringWithFormat:@"%ld", column]; } return nil; } @end 
+2
Jun 19 '15 at 0:32
source share

The above answers do not work for me, but after downloading the images I replaced

 [self.yourCollectionView reloadData] 

from

 [self.yourCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]]; 

to update, and it can display all cells correctly, you can try.

+2
Jun 26 '15 at 18:40
source share

I just ran into a similar issue with cell disappearing after scrolling UICollectionView on iOS 10 (there were no problems with iOS 6-9).

Subclassing UICollectionViewFlowLayout and overriding the layoutAttributesForElementsInRect: method does not work in my case.

The solution was quite simple. I am currently using an instance of UICollectionViewFlowLayout and set both itemSize and valuItemSize (I have not used valuItemSize before) and set it to some non-zero size. The actual size is calculated in the collection of the View: layout: sizeForItemAtIndexPath: method.

In addition, I removed the call to the invalidateLayout method from layoutSubviews to avoid unnecessary reloads.

+2
Oct 31 '16 at 9:44
source share



All Articles