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.
Related
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;
}
I try to make UICollectionView with cells, that intersect and partially overlay each other as it is done at screenshot:
This layout was reached by setting
self.minimumLineSpacing = -100;
at my UICollectionViewFlowLayout subclass.
When I scroll down, everything is OK. I see what I want. But when I scroll up, I see another behaviour, not like I expected:
So my question is: how can I make my layout look as at the first screen regardless scroll view direction.
Note: I have to support both iOS 6 and 7.
Thanks very much for any advices and any help.
Hmm, interesting. Since the collection view recycles cells, they are continuously added to and removed from the view hierarchy as they move on and off the screen. That being said, it stands to reason and when they are re-added to the view, they are simply added as subviews meaning that when a cell gets recycled, it now has the highest z-index of all of the cells.
One fairly pain-free way to rectify this would be to manually adjust the z position of each cell to be incrementally higher with the index path. That way, lower (y) cells will always appear above (z) the cells above (y) them.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellID = #"CELLID";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
if (cell.layer.zPosition != indexPath.row) {
[cell.layer setZPosition:indexPath.row];
}
return cell;
}
Found another sollution to solve this problem. We need to use UICollectionViewFlowLayout subclass.
#interface MyFlowLayout : UICollectionViewFlowLayout
#end
#implementation MyFlowLayout
- (void)prepareLayout {
[super prepareLayout];
// This allows us to make intersection and overlapping
self.minimumLineSpacing = -100;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *layoutAttributes = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *currentLayoutAttributes in layoutAttributes) {
// Change zIndex allows us to change not only visible position, but logic too
currentLayoutAttributes.zIndex = currentLayoutAttributes.indexPath.row;
}
return layoutAttributes;
}
#end
Hope that helps someone else.
I'm making a simple UICollectionView with a paging mechanism enabled, and everything works fine. Though, when scroll to the last page the number of the cells are not fully visible in the screen, and the last page contains some cells of the previous page.
How do I expand the contentSize of the UICollectionView so that the last page doesn't contain any cells of the previous page?
An example here: the UICollectionView scrolls horizontally with 6 cells, this way:
Page 1:
cell0 - cell1 - cell2 - cell3
Page 2:
cell4 - cell5 is expected, but unexpectedly
cell2 - cell3 - cell4 - cell5
How to change it?
SUMMARY:
I want to set
collectionView.contentSize = numberOfPage * collectionView.frame
NOT
collectionView.contentSize = numberOfCell * (cellFrame + spacing)
You need to subclass UICollectionViewLayout and override the collectionViewContentSize method. I subclassed UICollectionViewFlowLayout so I wouldn't have to re-write all the layout code.
I'm building a 4x4 grid, so my method looks like this:
- (CGSize)collectionViewContentSize
{
NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
NSInteger pages = ceil(itemCount / 16.0);
return CGSizeMake(320 * pages, self.collectionView.frame.size.height);
}
Side note, when you use a custom layout, you lose the ability to set the some of the display properties in the Interface Builder. You can set them programatically in the init method of your custom UICollectionViewLayout subclass. Here's mine for reference:
- (id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (void)setup
{
self.itemSize = CGSizeMake(65.0f, 65.0f);
self.minimumLineSpacing = 15;
self.sectionInset = UIEdgeInsetsMake(7.5f, 7.5f, 30.0f, 7.5f);
[self setScrollDirection:UICollectionViewScrollDirectionHorizontal];
}
For horizontal paging
if(CollectionView.pagingEnabled)
{
int numberOfPages = floor(collectionView.contentSize.width /
collectionView.frame.size.width) + 1;
CGFloat
float requierdWidth=self.collectionView.frame.size.width*numberOfPages;
self.Layout.footerReferenceSize=CGSizeMake(requierdWidth-self.collectionView.contentSize.width,0);
}
The answer works well, though for our code we had one section per page. So it meant the override for our layout class was just
-(CGSize) collectionViewContentSize {
return CGSizeMake(CGRectGetWidth(self.collectionView.frame) *
[self.collectionView numberOfSections],
CGRectGetHeight(self.collectionView.frame)) ;
}
I think you have to subclass UICollectionViewLayout and create your custom layout to manage these kind of problems.
I have an answer that doesn't require any subclassing.
In -viewDidLoad, calculate how many items per page you will have and how many pages you will have. Store these values in properties.
self.numberOfItemsPerPage = NumberOfRows * NumberOfColumns;
self.numberOfPages = ceilf((CGFloat)self.items.count / (CGFloat)self.numberOfItemsPerPage);
Then in -collectionView:numberOfItemsInSection: just lie to it:
return self.numberOfItemsPerPage * self.numberOfPages;
You will of course have more cells than content, right? So in -collectionView:cellForItemAtIndexPath: just return nil for the extra cells:
if (indexPath.item > [self.items count] - 1) {
//We have no more items, so return nil. This is to trick it to display actual full pages.
return nil;
}
There you go: full, scrollable final page. In my opinion, the horizontal scroll mode should just default to this.
You can adjust the content with the viewDidLayoutSubviews: method. This method gets called when the collection view and all the cells are placed in the view, so that you can adjust cell.