UICollectionView flowLayout not wrapping cells correctly - ios

I have a UICollectionView with a FLowLayout. It will work as I expect most of the time, but every now and then one of the cells does not wrap properly. For example, the the cell that should be on in the first "column" of the third row if actually trailing in the second row and there is just an empty space where it should be (see diagram below). All you can see of this rouge cell is the left hand side (the rest is cut off) and the place it should be is empty.
This does not happen consistently; it is not always the same row. Once it has happened, I can scroll up and then back and the cell will have fixed itself. Or, when I press the cell (which takes me to the next view via a push) and then pop back, I will see the cell in the incorrect position and then it will jump to the correct position.
The scroll speed seems to make it easier to reproduce the problem. When I scroll slowly, I can still see the cell in the wrong position every now and then, but then it will jump to the correct position straight away.
The problem started when I added the sections insets. Previously, I had the cells almost flush against the collection bounds (little, or no insets) and I did not notice the problem. But this meant the right and left of the collection view was empty. Ie, could not scroll. Also, the scroll bar was not flush to the right.
I can make the problem happen on both Simulator and on an iPad 3.
I guess the problem is happening because of the left and right section insets... But if the value is wrong, then I would expect the behavior to be consistent. I wonder if this might be a bug with Apple? Or perhaps this is due to a build up of the insets or something similar.
Follow up: I have been using this answer below by Nick for over 2 years now without a problem (in case people are wondering if there are any holes in that answer - I have not found any yet). Well done Nick.

There is a bug in UICollectionViewFlowLayout's implementation of layoutAttributesForElementsInRect that causes it to return TWO attribute objects for a single cell in certain cases involving section insets. One of the returned attribute objects is invalid (outside the bounds of the collection view) and the other is valid. Below is a subclass of UICollectionViewFlowLayout that fixes the problem by excluding cells outside of the collection view's bounds.
// 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 causes unnecessary recomputations and doesn't even completely solve the problem.
My solution completely solves the bug and shouldn't cause any problems when Apple fixes the root cause.

Put this into the viewController that owns the collection view
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self.collectionView.collectionViewLayout invalidateLayout];
}

i discovered similar problems in my iPhone application. Searching the Apple dev forum brought me this suitable solution which worked in my case and will probably in your case too:
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

A Swift 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 }
}
}

I've had this problem as well for a basic gridview layout with insets for margins. The limited debugging I've done for now is implementing - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect in my UICollectionViewFlowLayout subclass and by logging what the super class implementation 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 can also see that it seems to return the wrong values for itemIndexPath.item == 30, which is factor 10 of my gridview's number of cells per line, not sure if that's relevant.
- (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 a lack of time for more debugging, the workaround I've done for now is reduced my collectionviews width with an amount equal to the left and right margin. I have a header that still needs the full width so I've set clipsToBounds = NO on my collectionview and then also removed the left and right insets on it, seems to work. For the header view to then stay in place you need to implement frame shifting and sizing in the layout methods that are tasked with returning layoutAttributes for the header view.

I have added a bug report to Apple. What works for me is to set bottom sectionInset to a value less than top inset.

I was experiencing the same cell-deplacing-problem on the iPhone using a UICollectionViewFlowLayout and so I was glad finding your post. I know you are having the problem on an iPad, but I am posting this because I think it is a general issue with the UICollectionView. So here is what I found out.
I can confirm that the sectionInset is relevant to that problem. Besides that the headerReferenceSize also has influence whether a cell is deplaced or not. (This makes sense since it is needed for calcuating the origin.)
Unfortunately, even different screen sizes have to be taken into account. When playing around with the values for these two properties, I experienced that a certain configuration worked either on both (3.5" and 4"), on none, or on only one of the screen sizes. Usually none of them. (This also makes sense, since the bounds of the UICollectionView changes, therefore I did not experience any disparity between retina and non-retina.)
I ended up setting the sectionInset and headerReferenceSize depending on the screen size. I tried about 50 combinations until I found values under which the problem did not occure anymore and the layout was visually acceptable. It is very difficult to find values which work on both screen sizes.
So summarizing, I just can recommend you to play around with the values, check these on different screen sizes and hope that Apple will fix this issue.

I've just encountered a similar issue with cells disappearing after UICollectionView scroll on iOS 10 (got no problems on iOS 6-9).
Subclassing of UICollectionViewFlowLayout and overriding method layoutAttributesForElementsInRect: doesn't work in my case.
The solution was simple enough. Currently I use an instance of UICollectionViewFlowLayout and set both itemSize and estimatedItemSize (I didn't use estimatedItemSize before) and set it to some non-zero size.
Actual size is calculating in collectionView:layout:sizeForItemAtIndexPath: method.
Also, I've removed a call of invalidateLayout method from layoutSubviews in order to avoid unnecessary reloads.

I just experienced a similar issue but found a very different solution.
I am using a custom implementation of UICollectionViewFlowLayout with a horizontal scroll. I am also creating custom frame locations for each cell.
The problem that I was having was that [super layoutAttributesForElementsInRect:rect] wasn't actually returning all of the UICollectionViewLayoutAttributes that should be displayed on screen. On calls to [self.collectionView reloadData] some of the cells would suddenly be set to hidden.
What I ended up doing was to create a NSMutableDictionary that cached all of the UICollectionViewLayoutAttributes that I have seen so far and then include any items 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 the UICollectionViewLayoutAttributes are being saved 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

The above answers don't work for me, but after downloading the images, I replaced
[self.yourCollectionView reloadData]
with
[self.yourCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
to refresh and it can show all cells correctly, you can try it.

This might be a little late but make sure you are setting your attributes in prepare() if possible.
My issue was that the cells were laying out, then getting update in layoutAttributesForElements. This resulted in a flicker effect when new cells came into view.
By moving all the attribute logic into prepare, then setting them in UICollectionViewCell.apply() it eliminated the flicker and created butter smooth cell displaying ๐Ÿ˜Š

Swift 5 version of Nick Snyder answer:
class NDCollectionViewFlowLayout: UICollectionViewFlowLayout {
open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
var newAttributes = [AnyHashable](repeating: 0, count: attributes?.count ?? 0)
for attribute in attributes ?? [] {
if (attribute.frame.origin.x + attribute.frame.size.width <= collectionViewContentSize.width) && (attribute.frame.origin.y + attribute.frame.size.height <= collectionViewContentSize.height) {
newAttributes.append(attribute)
}
}
return newAttributes as? [UICollectionViewLayoutAttributes]
}
}
Or you could use extension of UICollectionViewFlowLayout:
extension UICollectionViewFlowLayout {
open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
var newAttributes = [AnyHashable](repeating: 0, count: attributes?.count ?? 0)
for attribute in attributes ?? [] {
if (attribute.frame.origin.x + attribute.frame.size.width <= collectionViewContentSize.width) && (attribute.frame.origin.y + attribute.frame.size.height <= collectionViewContentSize.height) {
newAttributes.append(attribute)
}
}
return newAttributes as? [UICollectionViewLayoutAttributes]
}
}

Related

Infinite Scrolling UITableView is glitchy when scrolling up after retrieving data from server

I have a feed that gets populated with 15 posts from the server. When I scroll down to 3 before the end of the list, I ping the server for the next 15 posts. This functionality works great. However, when I start scrolling up, the UITableViewCells frequently jump up, as though Cell 5 is now populating Cell 4, and Cell 4 is now populating Cell 3, etc. Either that, or the UITableView scroll is just jumping up.
When I get to the very top of the UITableView and then proceed to scroll down through all my data then back up, it works perfectly though. Is there a drawing issue with my table?
Edit: So, I've come across the understanding that this is happening because the heights of all my cells are dynamic. I'm pretty sure as I'm scrolling up, my UITableView is calculating and setting the appropriate heights, which is causing the jumpy action. I'm not sure how to mitigate that.
I never used the new funcionality of dynamic cell size in iOS8, but I can give you few suggestion for improve performance on table views. It should be a comment but it doesn't fit.
Cache the height of cells already displayed if you can. It's easy an dictionary paired with a sort of id would do the trick
Pay attention that you do not have complex layout between subviews of you cells
Check if you are drawing something that requires offscreen rendering, such as corner radius, clipping etc
I don't know how dynamic cell works on ios8 but I share piece of my code. It's pretty straightforward. I have a cell that I use as prototype, each times I need to calculate a cell height I feed it with my data, that I force it's layout to get me the correct height. Once I've got the height I saved it in a NSDictionary using the postID(it's a twitter like app) as a key.
This happens only when the cell height is not cached. If it is cached the height is returned.
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize size = CGSizeZero;
NSDictionary * data = self.timelineData[indexPath.row];
if (data[KEY_CELL_IDENTIFIER] == CellIdentifierPost) {
NSNumber * cachedHeight = [self.heightCaches objectForKey:#([(AFTimelinePostObject*)data[KEY_CELL_DATA] hash])];//[(AFTimelinePostObject*)data[KEY_CELL_DATA] timelinePostObjectId]];
if (cachedHeight) {
return (CGFloat)[cachedHeight doubleValue];
}
[_heightCell configureCellWith:data[KEY_CELL_DATA]];
size = [_heightCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
[self.heightCaches setObject:#(size.height) forKey:#([(AFTimelinePostObject*)data[KEY_CELL_DATA] hash])];//[(AFTimelinePostObject*)data[KEY_CELL_DATA] timelinePostObjectId]];
}
else if (data[KEY_CELL_IDENTIFIER] == CellIdentifierComment){
NSNumber * cachedHeight = [self.heightCaches objectForKey:#([(AFTimelinePostComments*)data[KEY_CELL_DATA] hash])];//[(AFTimelinePostObject*)data[KEY_CELL_DATA] timelinePostObjectId]];
if (cachedHeight) {
return (CGFloat)[cachedHeight doubleValue];
}
[_heightCommentCell configureCellWith:data[KEY_CELL_DATA]];
size = [_heightCommentCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
if (size.height < 80.0f) {
size = (CGSize) {
.width = NSIntegerMax,
.height = 115.f
};
}
else if (size.height > 180.0f) {
size = (CGSize) {
.width = NSIntegerMax,
.height = 180.f
};
}
[self.heightCaches setObject:#(size.height) forKey:#([(AFTimelinePostComments*)data[KEY_CELL_DATA] hash])];//[(AFTimelinePostObject*)data[KEY_CELL_DATA] timelinePostObjectId]];
}
else {
size = (CGSize) {
.width = NSIntegerMax,
.height = 50.f
};
}
return size.height;
}
Unfortunately, there does not seem to be a simple answer to this. I have struggled with it on multiple iOS apps.
The only solution I have found is to programmatically scroll to the top of your UITableView once it appears again.
[self.tableView setContentOffset:CGPointMake(0, 0 - self.tableView.contentInset.top) animated:YES];
OR
self.tableView.contentOffset = CGPointMake(0, 0 - self.tableView.contentInset.top);
Hope this an acceptable work around while still being able to use dynamic cell heights =)

UICollectionViewFlowLayout performance for Text Grid

I have an iOS 7 app using Storyboards that has the following structure:
UITabBarController->UINavgiationController->UICollectionViewController
I use a custom subclass of UICollectionViewFlowLayout to layout the grid. Each cell contains a single UILabel.
I am trying to render a 5 column grid in the UICVC. There can be upwards of 800 rows (sections, as one row per section). I've based this loosely on Erica Sadun's example code from her iOS book.
The cells all have specific, set widths (2 different widths used). All cells have the same height. The grid is wider than the physical display, and so scrolls horizontally and vertically.
It's all working fine, but performance has been very poor for more than about 30 rows. The problem comes when trying to calculate the custom layout, specifically in the following method:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *attribs = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *attributes = [NSMutableArray array];
[attribs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *currentLayout, NSUInteger idx, BOOL *stop)
{
NSString *layoutItemKey = [NSString stringWithFormat:#"%ld:%ld", (long)currentLayout.indexPath.section, (long)currentLayout.indexPath.item];
UICollectionViewLayoutAttributes *newLayout = [self.cachedLayoutAttributes objectForKey:layoutItemKey];
if (nil == newLayout)
{
newLayout = [self layoutAttributesForItemAtIndexPath:currentLayout.indexPath];
long section = currentLayout.indexPath.section;
long item = currentLayout.indexPath.item;
NSString *layoutItemKey = [NSString stringWithFormat:#"%ld:%ld", (long)section, (long)item];
[self.cachedLayoutAttributes setObject:newLayout forKey:layoutItemKey];
}
[attributes addObject:newLayout];
}];
return attributes;
}
The main delay here seems to come from a call to the iOS method:
layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
which is inside the layoutAttributesForItemAtIndexPath method:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGSize thisItemSize = [self sizeForItemAtIndexPath:indexPath];
CGFloat verticalOffset = [self verticalInsetForItemAtIndexPath:indexPath];
CGFloat horizontalOffset = [self horizontalInsetForItemAtIndexPath:indexPath];
if (self.scrollDirection == UICollectionViewScrollDirectionVertical)
attributes.frame = CGRectMake(horizontalOffset, verticalOffset, thisItemSize.width, thisItemSize.height);
else
attributes.frame = CGRectMake(verticalOffset, horizontalOffset, thisItemSize.width, thisItemSize.height);
return attributes;
}
Scrolling works fine to a point, then freezes for about 1.5 seconds while the next block of layout is calculated (always seems to be about 165 cells). As I'm caching everything, the next time the user scrolls performance is fine.
If I leave the cell widths to the UICollectionViewFlowLayout default everything flies, with no pauses.
To try to speed things up I have:
Ensured all views are opaque
Set the deceleration rate of the CollectionView to FAST
Made - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds return NO
I cache any UICollectionViewLayoutAttributes calculated
I cache the first 50 rows on initialisation. There's no noticeable lag doing this, and it allows the initial scroll performance to be a bit better than it would have been
I've run out of ideas for squeezing more performance out of the UICollectionViewFlowLayout.
Can anyone suggest how I can improve the code?
Thanks
Darren.
I've found a solution to the problem, and while it's not a complete answer to UICollectionView performance problems, it meets my needs.
I'm rendering a text grid. The rows are all the same height, the widths of the fields are always the same, and the overall width of a row is always the same.
Erica Sadun's code allows for variable height text cells, for text wrapping I assume. Because of that the layout needs to be calculated for each cell and row. For a grid with more than 100 cells the amount of time required is too long (on my iPhone 4S).
What I've done is to remove all the height and width calculation code, and replace it with fixed values I calculated manually.
This removes the bottleneck, and now I can render a grid with a few thousand cells without any noticeable lag.

CollectionView layout doesn't refresh until scroll

The best way to demonstrate my problem is with the video below:
http://foffer.dk/collectionview.mp4
As you can see, my collectionView doesn't update it's layout to correctly display the items until I start scrolling. I'm using a separate collectionViewFlowLayout model. Here is my code:
CollectionViewFlowLayout.m:
#implementation CollectionViewFlowLayout
- (void)prepareLayout {
CGFloat halfWidth = self.collectionView.bounds.size.width / 2;
CGFloat halfHeight = self.collectionView.bounds.size.height / 2;
self.itemSize = CGSizeMake(halfWidth, halfHeight);
self.minimumInteritemSpacing = 0;
self.minimumLineSpacing = 0;
}
// indicate that we want to redraw as we scroll
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
#end
my PhotosCollectionViewController (I've left out the standart collectionView stuff as I didn't think it is important to this issue, let me know if it is and I will post it):
-(instancetype)init {
UICollectionViewFlowLayout *layout = [[CollectionViewFlowLayout alloc] init];
return (self = [super initWithCollectionViewLayout:layout]);
}
-(void) viewDidLoad{
[super viewDidLoad];
[self.collectionView registerClass:[FOFPhotoCell class] forCellWithReuseIdentifier:#"photo"];
self.collectionView.backgroundColor = [UIColor whiteColor];
CGFloat bottomLayoutGuide = self.tabBarController.tabBar.frame.size.height;
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, bottomLayoutGuide, 0);
}
-(BOOL)shouldAutorotate{
return YES;
}
Do I need to call invalidateLayout somewhere? I can't seem to figure out where to implement that method. [self.collectionView invalidateLayout] doesn't work.
Could someone get me on the right track here?
Thanks in advance
Chris
So, it turns out I had it set up all wrong. As this article from apple says:
Before you start building custom layouts, consider whether doing so is
really necessary.
The UICollectionViewFlowLayout class provides a
significant amount of behavior that has already been optimized for
efficiency and that can be adapted in several ways to achieve many
different types of standard layouts. The only times to consider
implementing a custom layout are in the following situations:
The layout you want looks nothing like a grid or a line-based breaking
layout (a layout in which items are placed into a row until itโ€™s full,
then continue on to the next line until all items are placed) or
necessitates scrolling in more than one direction.
You want to change all of the cell positions frequently enough that it would be more work
to modify the existing flow layout than to create a custom layout.
And my layout config is really simple. So I was overcomplicating it.

Collection View,with custom layouts, cells misbehave on scrolling

I am trying to create custom tiled layout using UICollectionView.
It renders perfectly as desired in simulator once I run my app.
But the moment I scroll the view and bring it back all the cell's frame changes and the cells get overlapped, leaving spaces, randomly.
I am not able to solve this issue past 2 days.
Here goes the code from my custom layout class.
-(void)prepareLayout{
[self createCellSizeArray];//cellSizeArray holds cell sizes for all the cells(calculated statically)
[self createAttributeArrayOfAll];//attributeArrayOfAll holds attributes for all the cells and also calculates their frames using cellSizeArray
}
-(CGSize)collectionViewContentSize{
return CGSizeMake(768, 1500);//The size is static to check for scrolling, is this creating problems?
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewLayoutAttributes * layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
return layoutAttributes;
}
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *attArray =[NSMutableArray array];
for (NSInteger i =0; i< attributeArrayOfAll.count; i++) {
UICollectionViewLayoutAttributes * attribute = (UICollectionViewLayoutAttributes*)[attributeArrayOfAll objectAtIndex:i];
if(CGRectIntersectsRect(rect, attribute.frame)){
[attArray addObject:attribute];
}
}
return attArray;
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
Please help, Thanks in advance.
Edit:
In my [self createAttributeArrayOfAll]; I have these lines of code
CGRect frame = CGRectMake(_startNewRowPoint.x, _startNewRowPoint.y, cellSize.width, cellSize.height);
UICollectionViewLayoutAttributes * attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
attribute.alpha = 1.0;
attribute.frame = frame;
[attributeArrayOfAll addObject:attribute];
While I modified layoutAttributesForItemAtIndexPath:, to look something like this
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewLayoutAttributes * layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
layoutAttributes.frame = ((UICollectionViewLayoutAttributes*)[attributeArrayOfAll objectAtIndex:indexPath.item]).frame;
return layoutAttributes;
}
Moreover, the method layoutAttributesForItemAtIndexPath: never gets called implicitly. I even tried this:
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *attArray =[NSMutableArray arrayWithCapacity:attributeArrayOfAll.count];
for (NSInteger i =0; i< attributeArrayOfAll.count; i++) {
UICollectionViewLayoutAttributes * attribute = (UICollectionViewLayoutAttributes*)[attributeArrayOfAll objectAtIndex:i];
if(CGRectIntersectsRect(rect, attribute.frame)){
[attArray insertObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]] atIndex:i];
}
}
return attArray;
}
But still the result is the same distorted set of cells on scrolling.
I worked with 5 cells, first time it renders correctly, on scrolling away and then bringing it back in visible rect it gets distorted, if i scroll away again and bring it back in visible rect it renders correctly. However, when I do this with around 400 cells, once i scroll it never renders correctly. Even on reloading collection view, The cells gets distort. Please help.
Your layoutAttributesForItemAtIndexPath: method is not setting any properties of the layoutAttributes object before returning it. It needs to set frame (or center and size).
So finally managed a workaround!!! dequeue each cell with an unique Cell Identifier in cellForRow:
[self.summaryView registerClass:[BFSSummaryViewCell class] forCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d",CellIdentifier,indexPath.row]];
UICollectionViewCell *collectionCell = [collectionView dequeueReusableCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d",CellIdentifier,indexPath.row] forIndexPath:indexPath];
These two lines inside cellForRow worked for me, however with my collection view having around 1000 cells it increases the size of my application considerably. Lets hope apple fixes this bug asap.

UITableViewCell becomes unresponsive

I have a popover screen, with inside it :
a label, that may or may not appear (title)
a search bar, that may or may not appear
a label, that may or may not appear, and has a variable height (help label)
a scrollview, that may or may not appear, and has a variable height (some infos about the following table)
a table view
In order to present something nice, in viewDidLoad, I move the various frames to place the objects correctly and not have unused spaces cluttering my popover. Besides, I then resize the table (to take the most place needed), and the popover via contentSizeInPopover (to avoid having a near-empty huge popover). All that resizing seems to work nicely, but I have one big problem : with all that resizing done, some cells of my UITableView become unresponsive. One or two cells, usually the second one, only respond if i tap in their outer corners, but the rest of the cell completely ignore any touches.
I've tried everything : moving all to viewWillAppear, letting the autoresize do its job (doesn't seem to work either), but I still have this problem every time. I've found that if I comment the lines involved with changing the frame of the table, or the ones in contentSizeInPopover, the problem stops, but then my view is messed up, so this ins't a fix.
If anyone could give me something to get out of this mess, that would be awesome.
- (CGFloat)getHeightWithoutTable {
return LIST_TITLE_HEIGHT + (self.searchBar.hidden ? 0 : LIST_SEARCH_BAR_HEIGHT) + (self.helpLabel.hidden ? 0 : self.helpLabel.frame.size.height + LIST_STD_SPACE) + (self.errorScrollView.hidden ? 0 : self.errorScrollView.frame.size.height + LIST_STD_SPACE);
}
-(void)viewDidLoad {
[super viewDidLoad];
self.tableViewOutlet.backgroundView = nil;
self.originData = [NSMutableArray array];
self.searchedData = [NSMutableArray array];
if (self.helper != nil) {
CGFloat heightOffset = 0;
// Content
self.originData = [self.helper getData];
self.tableData = [NSMutableArray arrayWithArray:self.originData];
// Title
NSString *title = [self.helper getPopoverTitle];
if (title == nil) {
self.popoverTitle.hidden = YES;
heightOffset -= LIST_TITLE_HEIGHT;
} else {
self.popoverTitle.text = [self.helper getPopoverTitle];
}
// Search
if ([self.originData count] [self getStdHeight] / 3){
self.helpLabel.lineBreakMode = UILineBreakModeTailTruncation;
[self.helpLabel sizeThatFits:CGSizeMake(self.helpLabel.frame.size.width, [self getStdHeight] / 3)];
}
heightOffset += (self.helpLabel.frame.size.height - LIST_HELP_STD_HEIGHT);
}
// Errors
if ([self.helper respondsToSelector:#selector(getErrors)]) {
self.errors = [self.helper getErrors];
}
if (self.errors == nil || [self.errors count] == 0) {
self.errorScrollView.hidden = YES;
self.errorBg.hidden = YES;
heightOffset -= LIST_ERROR_STD_HEIGHT + LIST_STD_SPACE;
} else {
[self createErrorView];
heightOffset += (self.errorScrollView.frame.size.height - LIST_ERROR_STD_HEIGHT);
}
// Table
CGFloat previewHeight = LIST_CELL_HEIGHT * [self.tableData count] + LIST_STD_SPACE;
CGFloat remainingHeight = LIST_MAX_HEIGHT - [self getHeightWithoutTable] - LIST_STD_SPACE;
CGFloat tableHeight = MIN(previewHeight, remainingHeight);
CGRect tableFrame = self.tableViewOutlet.frame;
self.tableViewOutlet.frame = CGRectMake(tableFrame.origin.x, tableFrame.origin.y + heightOffset, LIST_WIDTH, tableHeight);
// Selected items
if ([helper getSelectedObject] != nil){
int index = [self.tableData indexOfObject:[helper getSelectedObject]];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableViewOutlet scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
}
- (CGSize)contentSizeForViewInPopover {
if (self.navigationController) {
return CGSizeMake(LIST_WIDTH, LIST_MAX_HEIGHT);
} else {
CGFloat totalHeight = [self getHeightWithoutTable] + self.tableViewOutlet.frame.size.height + LIST_STD_SPACE;
return CGSizeMake(LIST_WIDTH, totalHeight);
}
}
(gist if you need some coloring to help you)
An image of the nib :
Just a shot in the dark, since you have not provided any code. If you are adding things to the UITableCellView, just remember that a lot of components have their UserInteractionEnabled set to NO, which will disable the ability to interact with it. Make sure that any items you add to the cell that potentially take up the space where you are tapping (presumably the center of the cell?) have their UserInteractionEnabled set to YES.
The reason why the edges might still work is that the UITableCellView consists of 3 main parts, so you are probably only changing the center part.
Post some code then we can have a better look.
Found the answer myself : the fact I was using a self-filled UIScrollView next to my UITableView seemed to be the problem. As soon as I replaced the UIScrollView by a proper UITableView, the problem disappeared.

Resources