(EDIT : it seems to be working fine starting with iOS 9. I did not make extensive tests, but the example works. This confirms the bug present in iOS 8.)
I spent a lot of time testing UICollectionView's Flow Layout self sizing behavior. After a lot of frustration the issue is narrowed down to the fact that as soon as one sets the estimatedItemSize to a non-zero size, the scrolling no longer works properly.
In my example instead of showing 40 items it only displays 32.
I've copy pasted the code bellow. I've tested many things starting with a Swift version.
Basically it fails to calculate and/or properly update the layout's collectionViewContentSize()
Here is a complete demo http://git.io/AIrHNA
Anybody can point me in the right direction?
Thank you
#implementation ViewControllerObjcC
static NSString * const reuseIdentifier = #"Cell";
-(UICollectionViewFlowLayout*)flowLayout{
return (UICollectionViewFlowLayout*)self.collectionViewLayout;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
CGSize estimatedSize = CGSizeMake(self.view.frame.size.width, 25.0);
BOOL testEstimatedItemSize = true;
if (testEstimatedItemSize) {
[self flowLayout].estimatedItemSize = estimatedSize;
}else{
[self flowLayout].itemSize = estimatedSize;
}
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 40;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 40, 30)];
[cell.contentView addSubview:label];
label.text = [NSString stringWithFormat:#"%ld",(long)indexPath.row];
label.backgroundColor = [UIColor redColor];
return cell;
}
From Apple's Document,
Specifically, cells that are not onscreen are assumed to be the estimated height.
This is my guess.
When the estimated height is smaller than the actual cell height, the collection view's content size prediction is smaller than the actual content size.
Therefore it display only displays 32 of the total 40 cells.
In my project, all celsl are shown when a large estimated size is used.
From the documentation:
The default value of this property is CGSizeZero. Setting it to any
other value causes the collection view to query each cell for its
actual size using the cell’s
preferredLayoutAttributesFittingAttributes: method. If all of your
cells are the same height, use the itemSize property, instead of
this property, to specify the cell size instead.
It seems that you don't use this preferredLayoutAttributesFittingAttributes: method in your project?, have you tried implementing it?
On the other hand, this solution looks good:
Specifying one Dimension of Cells in UICollectionView using Auto Layout
I had the same issue. In my case, the space between items was 32. And my code was like this:
collectionViewLayout.minimumInteritemSpacing = 0
collectionViewLayout.minimumLineSpacing = 32
collectionViewLayout.scrollDirection = .horizontal
collectionViewLayout.itemSize = UICollectionViewFlowLayout.automaticSize
collectionViewLayout.estimatedItemSize = CGSize(width: 100, height: 32)
I changed minimumInteritemSpacing to 32 the same as minimumLineSpacing to fix the problem.
collectionViewLayout.minimumInteritemSpacing = 32
Related
I am newbie in iOS developing field. And i am facing trouble in creating custom collectionView cell. I have even tried changing Min spacing in size inspector from 10 to 2. But nothing is working out. Can anybody help me out.
And here is the attribute set for the custom cell.
attribute given for the custom cell
As per my undersanding, you have taken UICollectionView and use it delegate and datasource methods.
With delegate and datasource methods, use below method also.
return CGSizeMake(((self.view.frame.size.width-10(leftpadding)-10(top)-5(right)-10(bottom))/2) ,180);
Also Set Min spacing and Intents in IB of your storyboard.
As per my developing,my view is as below : -
Hope this will help you.
Download this project and import KTCenterFlowlayout.h and .m files into your project then write below code in view did load It Will work Fine
KTCenterFlowLayout *layout = [KTCenterFlowLayout new];
layout.minimumInteritemSpacing = 10.f;
layout.minimumLineSpacing = 10.f;
[self.collectionView setCollectionViewLayout:layout];
u can adjust the spacing between the cells by using like this
layout.minimumInteritemSpacing = 5.0;
layout.minimumLineSpacing = 5.0;
Change your code like this in the sizeForItemAtIndexPath method
-(CGSize) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
CGFloat minSpace = 10; //Space that you want
return CGSizeMake((screeWidth - minSpace) / 2 , cellHeight); //Cell height is your collectionViewCell height.
}
Hope this will help you.
I want to make a usual horizontalScrolling flowLayout UICollectionView with estimatedItemSize and preferredLayoutAttributesFittingAttributes in cell. But there is something wrong with last cell. Any idea where is the issue?
Project itself
#implementation RowCollectionView
- (instancetype) initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
{
if (self = [super initWithFrame:frame collectionViewLayout:layout])
{
[self configureRowCollectionView];
}
return self;
}
- (void) awakeFromNib
{
[super awakeFromNib];
[self configureRowCollectionView];
}
- (void) configureRowCollectionView
{
self.backgroundColor = [UIColor lightGrayColor];
self.dataSource = self;
self.delegate = self;
// Horizontal Direction
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionViewLayout;
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
// Estimated Item Size
flowLayout.estimatedItemSize = CGSizeMake(self.bounds.size.height, self.bounds.size.height);
[self registerClass:[RowCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass([RowCollectionViewCell class])];
}
- (NSInteger) collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 10;
}
- (UICollectionViewCell *) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([RowCollectionViewCell class]) forIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor redColor];
return cell;
}
#end
#implementation RowCollectionViewCell
- (UICollectionViewLayoutAttributes *) preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super preferredLayoutAttributesFittingAttributes:layoutAttributes];
UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
attributes.size = CGSizeMake(80, 80);
return attributes;
}
#end
I face a similar issue and the problem was solved by giving a proper minimum inter item spacing, using the delegate methods - minimumInteritemSpacingForSectionAt- of UICollectionViewDelegateFlowLayout.
You are setting estimatedItemSize in the init of view itself.
You need to set it in some controller.
Also,
If all of your cells are the same height, use the itemSize property, instead of this property, to specify the cell size instead.
Documentation: estimatedItemSize
there is a simple method to resolve this. You can add number of prototype cells to check the cell at required position. Once you find the issue at last cell . Check the cell insets in Inspector window.
You call super method but you did not use super returned layoutAttributes.
[super preferredLayoutAttributesFittingAttributes:layoutAttributes];
You can try to print out original layoutAttributes vs super's layoutAttributes.
Sometimes, you don't need to call super function.
Second, You can create custom flowlayout or set inset to let your cell align top. I did this in my project.
You can consider it a Suggestion. According to me the height of UICollectionView is more than UICollectionViewCell Height, thats why its happening. please make them equal them
Custom cell size must be same as that of collection view cell,please check that.It may solve the problem for you.
I have done the similar small project (one raw (1*N) horizontal collection view), here is the github. I hope it would be helpful for your requirement.
https://github.com/texas16/HorizontalCollectionView
I had the same issue and the simplest solution in case you have an horizontal collection having that issue is to make the collection height equal to the items height.
Had same problem and what fix it in my case was to make sure :
All cells height are equal.
The collectionView height is bigger then cell height + space between cells.
The code I used to create a rectangle (at least until iOS7) was
CGRect rect = [cTableView frame];
rect.origin.y += [cTableView rowHeight];
searchOverlayView = [[BecomeFirstResponderControl alloc] initWithFrame:rect];
On iOS7, cTableView (an instance of a UITableView) returned 44. Testing in iOS8 with an iPhone 5s returns -1.
Why is this happening? What is the correct code that needs to be used in order for my app to be backwards compatible with iOS7?
Apple changed the default row height in iOS8 to UITableViewAutomaticDimension, which is declared as -1. This means that your table view is set up for automatic cell height calculation.
You will either need to implement autoLayout (recommended) or implement the new delegate method: heightForRowAtIndexPath. Here's a great question about auto layout: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
Seems like you were effectively hard coding 44 (the old default) anyway, though, so you could just do that (not recommended).
This made me struggle for hours. I ended up hard coding the value to 44:
self.tableView.rowHeight = 44;
There is a performance penalty for implementing heightForRowAtIndexPath that I prefer not to incur when all rows in a table are the same height and never change at runtime (it is called once for every row, each time the table is displayed).
In this situation, I continue to set "Row Height" in the XIB and use the following iOS 8 friendly code when I need rowHeight (it works on iOS 7 and below too).
NSInteger aRowHeight = self.tableView.rowHeight;
if (-1 == aRowHeight)
{
aRowHeight = 44;
}
This allows you to keep freely editing Row Height in the XIB and will work even if Apple fixes this bug/feature in the future and a XIB set Row Height = 44 stops coming back as -1.
If you accidentally change the row height in IB from 44 to something else (like 40), automatic cell size calculation fails. You owe me 3 hours, Apple.
My solution to this problem:
#interface MCDummyTableView () <UITableViewDataSource, UITableViewDelegate>
#end
#implementation MCDummyTableView
- (instancetype) initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
frame = (CGRect){ 0, 0, 100, 100 };
self = [super initWithFrame:frame style:style];
if(!self) return self;
self.dataSource = self;
self.delegate = self;
[self registerClass:[UITableViewCell class] forCellReuseIdentifier:#"CELL"];
return self;
}
- (NSInteger) numberOfSections {
return 1;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (UITableViewCell*) cellForRowAtIndexPath:(NSIndexPath*)indexPath {
/*
UITableView doesn't want to generate cells until it's in the view hiearchy, this fixes that.
However, if this breaks (or you don't like it) you can always add your UITableView to a UIWindow, then destroy it
(that is likely the safer solution).
*/
return [self.dataSource tableView:self cellForRowAtIndexPath:indexPath];
}
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
return [self dequeueReusableCellWithIdentifier:#"CELL"];
}
- (CGFloat) defaultRowHeight {
return [self cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].frame.size.height;
}
#end
I really don't like hardcoding things. I use this class to cache the default cell height early on in the app.
One more consideration is that if you are calculating the height based on existing view dimensions, the heightForRowAtIndexPath method may be called before viewDidLayoutSubviews.
In this case, override viewDidLayoutSubviews, and recalculate the frame.size.height value for all the visible cells.
I am trying to add a header to a collection view. I am using a custom layout that scrolls horizontally, it is used to view a list of avatar images for friends. I can get the header to appear but it does NOT dequeue. As soon as the header view goes off screen, its gone for good. Can anyone figure out why this is?
Thank you!
Collection View data source:
- (UICollectionReusableView *)collectionView:(SWAvatarViewerCollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath
{
if (self.showAddAvatarHeaderView && [kind isEqualToString:UICollectionElementKindSectionHeader]) {
return [collectionView dequeueAddAvatarViewHeaderForIndexPath:indexPath];
}
return nil;
}
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(SWAvatarViewerCollectionViewFlowLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
if (!self.showAddAvatarHeaderView) {
return CGSizeZero;
}
return CGSizeMake(kSWAvatarViewerAddAvatarHeaderViewWidth, CGRectGetHeight(collectionView.bounds));
}
Avatar collection view:
- (SWAvatarViewerAddAvatarHeaderView *)dequeueAddAvatarViewHeaderForIndexPath:(NSIndexPath *)indexPath {
SWAvatarViewerAddAvatarHeaderView *headerView = [super dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:[SWAvatarViewerAddAvatarHeaderView headerReuseIdentifier]
forIndexPath:indexPath];
headerView.delegate = self;
return headerView;
}
Nib file registration:
[self registerNib:[SWAvatarViewerAddAvatarHeaderView nib]
forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:[SWAvatarViewerAddAvatarHeaderView headerReuseIdentifier]];
Layout:
#pragma mark - Initialization
- (void)configureFlowLayout {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
// Padding for cells is taken into account in the cell's layout. Remove all
// padding between cells
self.sectionInset = UIEdgeInsetsMake(0, 00.0f, 0, 00.0f);
self.minimumLineSpacing = 0.0f;
self.minimumInteritemSpacing = CGFLOAT_MAX;
_cellBottomLabelFont = [UIFont systemFontOfSize:12.0];
CGSize defaultAvatarSize = CGSizeMake(44.0f, 44.0f);
_avatarViewSize = defaultAvatarSize;
_springinessEnabled = YES;
_springResistanceFactor = 1000;
}
You're apparently using a third-party layout I've never heard of. After some discussion with you, my feeling is that my first comment under your question was probably right: the layout itself may be buggy.
In a collection view, the layout attributes of the cells (position, size, transform, alpha, etc.) are the responsibility of the layout. So if something disappears merely because it is scrolled off the screen and then back on, it sounds like the layout itself is not doing its job correctly.
Quick googleing did not unveil the SWAvatarViewerCollectionViewFlowLayout. If you have the sources, you can take a look at the layout code, there should be a method called layoutAttributesForElementsInRect:.
The collection view is dequeuing items as soon as they go offscreen which is determined with the help of the aforementioned method (The layout attributes contain the views center and dimensions). If the method returns always the layout attributes for the header it will not get dequeued.
However if you do not have access to the code (like a static lib) you probably can not do much about it.
Just put this method that will solve all the problems
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(-50, 10, 10, 10); //asuming
//UIEdgeInsetsMake(<#CGFloat top#>, <#CGFloat left#>, <#CGFloat bottom#>, <#CGFloat right#>)
}
Enjoy.
I'm building a status item kind of thing for a UICollectionView. My problem is when I want to add some text to the status area I can't get the thing to auto resize to the new text. I have auto layout on and I've tried all kinds of things found on stacky.
The one which I think is the closest to being correct is this:
-(UICollectionViewCell *) collectionView:(UICollectionView*)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
StatusItemModel *statusModel = [self.items objectAtIndex[indexPath indexPosition:0]];
StatusItemEventCell *statusCell = [collectionView dequeueResusableCellwithReuseIdentifier: #"EventStatusItem" forIndexPath:indexPath];
statusCell.statusTitleLabel.text = [statusModel.statusDetails valueForKey:#"title"];
statusCell.statusContentTextView.text = [statuaModel.statusDetails valueForKey:#"content"];
[statusCell layoutIfNeeded];
return statusCell;
}
// After which I believe we have to do some magic in this but what?
- (CGSize) collectionView:(UiCollectionView *) collectionView layout:(UICollectionViewLayout *) collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
// How do I get the size of the textview statusContentTextView.text?
// With that I'll be able to figure out what needs to be returned.
return CGSizeMake(299.f, 200.f);
}
The autolayout is setup with constraints for all elements in the cell. I've even played around with the intrinsic size and placeholders, however still now luck. Please can someone point me in the right direction.
So after going around in circles thinking there was a better way, no we need to know the size before we can set the size of the cell for the collection view. Pretty counter productive, because sometimes we don't know the size of it at run time. The way I solved this was to create a mock UITextView object and then called sizeThatFits.
So here is what I did with my code:
- (CGSize) collectionView:(UICollectionView *) collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
StatusItemModel *statusModel = [self.items objectAtIndex:[indexPath indexAtPosition:0]];
UITextView *temporaryTextView = [[UITextView alloc] init];
temporaryTextView.text = [statusModel.statusDetails valueForKey:#"content"];
CGSize textBoxSize = [temporaryTextView sizeThatFits:CGSizeMake(299.0f, MAXFLOAT)];
// Can now use the height of the text box to work out the size of the cell and
// the other components that make up the cell
return textBoxSize;
}