Get number of rows in UICollectionView - ios

UICollection view automatically adjusts the number of rows based on the number of items per section and size of each cell.
So is there a way to get the number of rows in an UICollectionView?
For example: If I have a calendar with 31 days that automatically fit into n rows.
How do I get the value of this 'n' ?

You can get the position of an item after it's laid out with [myCollectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:]
If you don't know the dimensions of your items, or don't want to assume that you do, one approach would be to iterate over the items in your collection and look for a step change in the Y-position of the item. Obviously this only works if you're using a grid-based layout.
This does the trick:
NSInteger totalItems = [myCollectionView numberOfItemsInSection:0];
// How many items are there per row?
NSInteger currItem;
CGFloat currRowOriginY = CGFLOAT_MAX;
for (currItem = 0; currItem < totalItems; currItem++) {
UICollectionViewLayoutAttributes *attributes =
[collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:
[NSIndexPath indexPathForItem:currItem inSection:0]];
if (currItem == 0) {
currRowOriginY = attributes.frame.origin.y;
continue;
}
if (attributes.frame.origin.y > currRowOriginY + 5.0f) {
break;
}
}
NSLog(#"new row started at item %ld", (long)currItem);
NSInteger totalRows = totalItems / currItem;
NSLog(#"%ld rows", (long)totalRows);
If you do know the dimensions of your items, you could get the position of the last item with
NSInteger totalItems = [self.timelineCollectionView numberOfItemsInSection:0];
NSIndexPath lastIndex = [NSIndexPath indexPathForItem:totalItems - 1 inSection:0];
UICollectionViewLayoutAttributes *attributes =
[myCollectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:lastIndex];
// Frame of last item is now in attributes.frame
Then take that last item's dimensions and divide by your known row height. Don't forget to account for any headers or spacing. These properties are also available from the UICollectionViewFlowLayout.
Pull the flow layout out with
UICollectionViewFlowLayout *myFlowLayout = (UICollectionViewFlowLayout*)myCollectionView.collectionViewFlowLayout;
And look in
myFlowLayout.headerReferenceSize
myFlowLayout.minimumLineSpacing
and so on.

Related

Trying to dynamically increase height of UITableView while user adds rows to UITableView in iOS

I am working on an app where the user is able to add rows to the UITableView dynamically. This part is working fine. However, on my iOS screen, the UITableView is of a static height regardless of how many rows there are. Once the number of rows increases beyond a certain point, the user must scroll to see the additional cells.
What I would like to do is dynamically increase the height of the table with each row the user adds. So for example, if I have a tableView, if the user adds one row, I would like the tableview to be 50px tall, if the user adds 2 rows, the table dynamically increases in height to 100px, until it reaches say, a maximum height of 250px. My code for adding the rows dynamically, is as follows:
- (IBAction)addRow:(id)sender {
NSString *theObjectToInsert = [NSString stringWithFormat:#"Row #: %lu", (unsigned long)[self.tableData count]];
[self.tableData addObject:theObjectToInsert];
NSIndexPath *newPath=[NSIndexPath indexPathForRow:self.tableData.count-1 inSection:0];
[self.choiceTable insertRowsAtIndexPaths:#[newPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.choiceTable scrollToRowAtIndexPath:newPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
How do I incorporate the dynamic growth of the UITableView along with the increasing number of rows?
Just to be clear, when you say the table height is statically set right now I'll assume that is not desired and you do want it to be dynamic.
What if recommend doing is every time you add a row, check the height of all of the rows already added. You can do that by calling methods already implemented in the UITableViewDataSource protocol.
CGFloat maxDynamicTableHeight = 250.0f;
NSInteger numberOfSections = [self numberOfSectionsInTableView:self.tableView];
CGFloat runningHeight = 0.0f;
for (int section = 0; section < numberOfSections && runningHeight < maxDynamicTableHeight; section++) {
NSInteger numberOfRows = [self tableView:self.tableView numberOfRowsInSection:section];
for (int row = 0; row < numberOfRows && runningHeight < maxDynamicTableHeight; row++) {
runningHeight += [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section]];
}
}
CGRect frame = self.tableView.frame;
frame.size.height = (runningHeight > maxDynamicTableHeight) ? maxDynamicTableHeight : runningHeight;
Self.tableView.frame = frame;
Sorry if there are any glaring issues, I'm on mobile so I can't test this right now.

Create a view using collection view in ios

I am trying to create a view similar to the attached image below. there is a variable sized width. I have marked text as black as there is a copyright issue.
Can anyone please look into the same and put some code so that it can help me somewhere.
Do I need to implement Custom Collection View Layout?
Please help me.
This is response to your comment you need to add 3 extra lines of code in SGSStaggeredFlowLayout
NSArray* arr = [super layoutAttributesForElementsInRect:rect];
// THIS CODE SEPARATES INTO ROWS
NSMutableArray* rows = [NSMutableArray array];
NSMutableArray* currentRow = nil;
NSInteger currentIndex = 0;
BOOL nextIsNewRow = YES;
for (UICollectionViewLayoutAttributes* atts in arr) {
if (nextIsNewRow) {
nextIsNewRow = NO;
if (currentRow) {
[rows addObject:currentRow];
}
currentRow = [NSMutableArray array];
}
if (arr.count > currentIndex+1) {
UICollectionViewLayoutAttributes* nextAtts = arr[currentIndex+1];
if (nextAtts.frame.origin.y > atts.frame.origin.y) {
nextIsNewRow = YES;
}
}
[currentRow addObject:atts];
currentIndex++;
}
if (![rows containsObject:currentRow]) {
[rows addObject:currentRow];
}
It works like charm :)
You can set size for every item by impelmenting
UICollectionViewDelegateFlowLayout protocol, and calculate item width using even/odd formula.
-(CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
NSInteger itemsPerRow = 0;
NSInteger contentSizeWidth = 0;
NSInteger num = indexPath.row;
if (num % 2)
{// odd
itemsPerRow = 2;
}
else {
// even
itemsPerRow = 3;
}
contentSizeWidth = collectionView.frame.size.width- (flowLayout.minimumInteritemSpacing*(itemsPerRow-1))-flowLayout.sectionInset.left-flowLayout.sectionInset.right;
return CGSizeMake(contentSizeWidth/itemsPerRow, 100);
}
if you are trying to do this without any framework, you need to develop your own algroithm to calculate the width of each cell.
first, you need to calculate the width of text plus margin maybe border as well.
Second, calculate how many items are gonna be placed in given row. try to add 3 togther , if the total width excess the uicollection width, it means the third text should go to the next cell. if it is less than the collection width,it means you can add try to the 4th text.
third, caculate the width of each cell in each line base on how many cells are gonna placed on that line and their own width.
changing the uicollectionview width should not be diffculty since collectionviewcells are darw from left to right then top to bottom.

Collapsing UICollectionView

I'm having a fight with UICollectionView to adopt it to my needs.
I'm trying to build a collapsing tag cloud. All elements can dynamically change size based on text inside.
When collection is displayed, I would like to display only first row and hide rest of content.
If there is more content than just for one row I want to show a button as a last item in first row - after selecting it, I will change collection size to fit it's content.
First step:
After click:
I was able to achieve desired effect with this code:
- (void) updateFrame
{
if(self.showFull)
{
self.showMoreButton.hidden = YES;
self.bottomConstrain.constant = 0;
self.frame = CGRectMake(0, 0, self.collectionView.frame.size.width, self.collectionView.contentSize.height);
}
else
{
self.showMoreButton.hidden = NO;
self.bottomConstrain.constant = self.originalConstrainValue;
self.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
}
But button in my case is placed completely outside the collection:
I'm hitting my head against a brick wall how to make this button to be a part of collection view.
It's the first time I'm playing with UICollectionView and UICollectionViewFlowLayout and it's too complex for me still to understand where would be best spot to put it.
One idea I had was to play a button over UICollectionView and position it at the end of first row - but I don't know how to get first row size from collection. With this approach it will be problematic also to cover last item in a row.
I think the best will be to put this element as a UICollectionViewCell, but I have no idea how to approach that - how to predict where it will placed and how to hide it later.
Any ideas will be highly appreciated.
Current template project is here:
https://www.zipshare.com/download/eyJhcmNoaXZlSWQiOiJjNTg2MGFkNC1mYWYxLTRlMzItOTA1YS1hMWFjOGFkMjMzYjUiLCJlbWFpbCI6Imdya3J1a293c2tpQGdtYWlsLmNvbSJ9
I'd suggest putting this button as a different cell INSIDE the collection view. Then, in collectionView:didSelectItemAtIndexPath: you would check to see if the special item was selected and react accordingly. I would let this button always be the last item in the collection view so you know where it is. When the items have expanded and everything is displayed, make sure you flip your logic and compress the collection view when this special cell is pressed.
You'll need to save some state to do this:
1.The array of tags
whether or not the collection view is expanded or not
Once you have this info, you can just flip the bits and reload the collection view section.
I've managed to achieve what I wanted with this code (thanks for atreat for suggestion).
I'm estimating which element won't fit in first row, and pushing "More" tag into tags array.
There is some logic also to make sure that More button will fit after last element in this row, if not it's pushed instead of this element. Maybe some will find it usefull.
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.collectionView.collectionViewLayout;
CGFloat collectionWidth = self.frame.size.width;
CGFloat lastFittingItemRight = 0.0f;
CGFloat spacingBetweenElements = flowLayout.minimumInteritemSpacing;
CGFloat sectionInsetLeft = flowLayout.sectionInset.left;
CGFloat sectionInsetRight = flowLayout.sectionInset.right;
CGFloat rightEdge = sectionInsetLeft;
for (int i = 0; i < [self collectionView:self.collectionView numberOfItemsInSection:0]; i++)
{
CGSize elementSize = [self collectionView:self.collectionView layout:flowLayout sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
CGFloat elementWidth = elementSize.width;
rightEdge += elementWidth;
if(rightEdge > collectionWidth - sectionInsetRight)
{
self.moreButtonIndexPath = [NSIndexPath indexPathForItem:i inSection:0];
break;
}
lastFittingItemRight = rightEdge;
rightEdge += spacingBetweenElements;
}
if(self.moreButtonIndexPath)
{
NSString* showMoreText = #"More";
CGSize moreButtonSize = [self sizeForTagWithText:showMoreText];
CGFloat rightEdgeMoreButton = lastFittingItemRight + spacingBetweenElements + moreButtonSize.width;
NSInteger moreButtonIndex = self.moreButtonIndexPath.row;
if(rightEdgeMoreButton > collectionWidth - sectionInsetRight)
{
moreButtonIndex = moreButtonIndex-1;
}
[self.tags insertObject:showMoreText atIndex:moreButtonIndex];
}

How to get number of rows in a section in ios

I am working on collection view and I want number of rows in previous section.
Is there any method which return number of row in section.
Please help me.
The delegate method of the UICollectionView dataSource that you will need to implement in order to display the cells can be invoked manually.
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
Therefore you can call it using:
NSInteger numberOfRows = [self collectionView:self.collectionView numberOfItemsInSection:MAX(0, currentSection - 1)];
You do not want to get an out of bounds exception so I have capped it at 0*
EDIT
#holex raises a very good point - I was assuming you only had one column.
If you know the size of your items width you can do the following:
//Assuming a fixed width cell
NSInteger section = 0;
NSInteger totalItemsInSection = [self.feedCollectionView numberOfItemsInSection:section];
UIEdgeInsets collectionViewInset = self.feedCollectionView.collectionViewLayout.sectionInset;
CGFloat collectionViewWidth = CGRectGetWidth(self.feedCollectionView.frame) - collectionViewInset.left - collectionViewInset.right;
CGFloat cellWidth = [self.feedCollectionView.collectionViewLayout itemSize].width;
CGFloat cellSpacing = [self.feedCollectionView.collectionViewLayout minimumInteritemSpacing];
NSInteger totalItemsInRow = floor((CGFloat)collectionViewWidth / (cellWidth + cellSpacing));
NSInteger numberOfRows = ceil((CGFloat)totalItemsInSection / totalItemsInRow);
NSLog(#"%#", #(numberOfRows));
We determine how many items will appear in a row, to be a able to determine the amount of rows in the section.
There are two method in collectionview, notice that the method are not in the delegate of collectionview.
- (NSInteger)numberOfSections;
- (NSInteger)numberOfItemsInSection:(NSInteger)section;
You can call [self.collectionView numberOfSections]; to find the sectionCount.
You can call [self.collectionView numberOfItemsInSection:somesection]; to find the rows in some section.

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.

Resources