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.
Related
I am adding new rows in my table view and resizing it according to its contents, but after the resize, the cell contents, like a button and didSelectRowAtIndexPath: are not being invoked.
Here is the resizing code:
CGRect frame = cell.replayTableView.frame;
int height = (model.replyComments.count*61)+3;
frame.size = CGSizeMake(cell.replayTableView.frame.size.width, height);
cell.tableviewReplay.refTable.frame=frame;
Assign a height on heightForRowAtIndexPath
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.Row == 1)
return 50.0;
else if (indexPath.Row == 2)
return 60;
}
First if you are using auto layouts you have to update constraints also .
In your code you are getting frame from a different view
CGRect frame = cell.replayTableView.frame;
after changing height
CGRect frame = cell.replayTableView.frame;
int height = (model.replyComments.count*61)+3;
frame.size = CGSizeMake(cell.replayTableView.frame.size.width, height);
but then setting it to different view
cell.tableviewReplay.refTable.frame=frame;
This may be creating issue for you.
// Declare a variable in yourController.h CGFloat tableHeight;
// set height in heightforRowAtindex
// run loop for number of rows in ur tableview data array
// put this code when ur data array is not empty i.e after responce of webservice etc
// as your tableview is and custom cells are not created programatically , so you need viewDidLayoutSubviews to reset frames
tableHeight = 0.0f;
for (int i = 0; i < [dataArray count]; i ++) {
tableHeight += [self tableView:self.multipleArticletableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
}
[self viewDidLayoutSubviews];
// put code below in viewDidLayoutSubviews method . it will work
self.multipleArticletableView.frame = CGRectMake(self.multipleArticletableView.frame.origin.x, self.multipleArticletableView.frame.origin.y,self.multipleArticletableView.frame.size.width, tableHeight);
Most common cause for this kind of issues are cells having some UI components that intercepts the user tap and do not pass on the tap to underneath cell. You can fix this by disabling user interaction on those components.
For example if you had added a UIView in your cell then call
otherView.userInteractionEnabled = NO;
Am using UICollectionView and changing the size of the item in the delegate method of collection view like this
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
lastIndex = 0;
layoutDiff = 4;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == (lastIndex + layoutDiff) - 1){
layoutDiff = (layoutDiff == 4)?5:4;
lastIndex = indexPath.row + 1;
return CGSizeMake(213.2, 213.2);
}
return CGSizeMake(106.6, 106.6);
}
and here is my current screen.
and all the below cells to be follow this behaviour, means the pattern i want is like one collection view item to big depending on my logic where i want to make it big but i don't want this space. I think if the space will not b there then all things will be right. Can anyone help with this?
FlowLayout can't help with this.
You hava to implement your subclass of UICollectionLayout.
Here are some articles that take about custom Collection View Layout.
Custom Collection View Layouts.
https://www.objc.io/issues/3-views/collection-view-layouts/
Knowing When to Subclass the Flow Layout
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html#//apple_ref/doc/uid/TP40012334-CH3-SW4
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've got this issue. I'm using a collection view as a subview of the main view of a UIViewController. The collection view is pinned to the superview size, thus it has 4 constraints for lead,trail, top and bottom with constants equal to 0.
The cell is a subclass to UICollectionViewCell and is composed by:
first header view
second header view
UITableView
The collection view cell is loaded from xib and the interface of the views is made for 4 inches devices.
The first header view is pinned at top, trail, lead with constant 0 and fixed height to its superview.
The second header view is constrained to first header view with vertical spacing equal to 0, fixed height, pinned trail, lead with constant 0 to its superview.
The table view is constrained to second header view with vertical spacing equal to 0 and pinned trail, lead and bottom with constant 0 to its superview.
On 4 inches screen everything is OK, but when I load on 3.5 I have some problems, the fact is that I want to create UITableViewCell with a dynamic height. The height should be the height of the UITableView dived by the number of rows, in this way they will appear on screen.
The table view delegate and datasource are valorized only when I set the data that need to be loaded and that happens after creating it, but before the collection view cell is returned from the data source method.
In the -layoutSubviews method of the UICollectionViewCell subclass I set the tableview row height, to the desired value that is
- (void) layoutSubviews {
[super layoutSubviews];
self.answerTableView.rowHeight = self.answerTableView.bounds.size.height / self.quizSection.answers.count;
}
The fact is that here the table view is stil not resized thus the resulting rowHeight is wrong, the value is the same I receive in 4 inches display. The superview (the contentView of the collection view cell) is ok, but the table view has a size that is not correct. When displayed the collection view is fine except for the fact that the table view's cells are wrong in size.
So I started to trace the cycle of the collection view cell.
In the initWithFrame methods the collection view cell has the same size of the original xib (4 inches screen)
In the view controller if I ask the size of the collection view cell right after dequeuing, if resized to the correct screen size, but the table view inside not
After loading the data and setting delegate and datasource, collection view is ok, but tableview not
In the -updateConstraints of the collection view cell the table view size is still wrong
In the -layoutSubviewsof the collection view cell the table view size is still wrong
The first call of the dtasource method of the table view returns a correct size
I've found the solution that is use the delegate methods - (float) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath, but I'm really curios why the other approach doesn't work can someone explain why?
THX
[CODE FOR HELP]
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) { // Initialization code
NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
if ([arrayOfViews count] < 1) { return nil; }
if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[UICollectionViewCell class]]) { return nil; }
self = [arrayOfViews objectAtIndex:0];
[self.answerTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:AnswerCellIdentifier];
}
return self;
}
- (void) layoutSubviews {
[super layoutSubviews];
self.answerTableView.rowHeight = self.answerTableView.bounds.size.height / self.quizSection.answers.count; //WRONG
}
- (void) updateConstraints {
[super updateConstraints];
self.answerTableView.rowHeight = self.answerTableView.bounds.size.height / self.quizSection.answers.count; //WRONG
}
#pragma mark -
#pragma mark custom getter/setter
- (void) setQuizSection:(QuizSection *)quizSection {
if (_quizSection == quizSection) {
return;
}
_quizSection = quizSection;
//Set data
self.questionLabel.text = _quizSection.question[KEY_TEXT];
self.answerTableView.delegate = self;
self.answerTableView.dataSource = self;
}
#pragma mark -
#pragma mark TableViewDelegate TableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.quizSection.answers.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:AnswerCellIdentifier];
NSDictionary * answerDict = self.quizSection.answers[indexPath.row];
cell.textLabel.text = answerDict[KEY_TEXT];
return cell;
}