Customizing UICollectionViewCell depending on indexPath - ios

Hi i'm trying to customize my collectionviewcell depending on its indexPath but also if I set
if (indexPath.row == 0)
{
[cell addSubView: view];
}
the view appear random in some cells.
This is the code I'm using
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 15;
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
NSInteger row = indexPath.row;
UIView *contentCell = [[UIView alloc] initWithFrame:cell.frame];
if (row == 0)
{
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(1.0f, 1.0f, 50.0f, 50.0f)];
label.text = #"Test";
[contentCell addSubview:label];
}
[cell addSubview:contentCell];
cell.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"container"]];
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%d", indexPath.row);
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(414, 228);
}

You have to read about reusable cells.
you can avoid the problem for now by doing this.
if (row == 0)
{
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(1.0f, 1.0f, 50.0f, 50.0f)];
label.text = #"Test";
label.tag = 200;
[contentCell addSubview:label];
}else{
UIView* lbl = [contentCell viewWithTag:200];
if(lbl)
[lbl removeFromSuperView];
}
but this will affect the scrolling and memory performance, you can put the label in the cell by default && show/hide it in the if/else blocks

You are adding view several times as a subview. Cells are reused due to "dequeueReusableCellWithReuseIdentifier", so after you scroll up and down, you get back existing view, which already has subview added.
You should read more about reusing cells.
One way to avoid that behavior is to create all views when you create a cell and just show / hide them.
Otherwise create cells with different identifier for different rows - in your case for row 0.

Related

Single CollectionView for Multi Tab SegmentControl

I want to display two collection view using segment controller, on change of segment second collection view will display.
my code is :
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"morningcell" forIndexPath:indexPath];
UICollectionViewCell *cell1 = [collectionView dequeueReusableCellWithReuseIdentifier:#"eveningcell" forIndexPath:indexPath];
if (_segment.selectedSegmentIndex == 0) {
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, cell.frame.size.height,cell.frame.size.width)];
lbl.text = [numbers objectAtIndex:indexPath.row];
[cell addSubview:lbl];
[lbl setTextAlignment:UITextAlignmentCenter];
return cell;
}
else{
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, cell1.frame.size.height,cell1.frame.size.width)];
lbl.text = [numbers objectAtIndex:indexPath.row];
[cell1 addSubview:lbl];
[lbl setTextAlignment:UITextAlignmentCenter];
return cell1;
}}
Do not create two collectionView for the task that you want because you can achieve it using a single collectionView.
#interface YourVC(), UICollectionViewDelegate, UICollectionViewDataSource {
NSArray *arrayOfModelsForFirstCollectionView;
NSArray *arrayOfModelsForSecondCollectionView;
}
#end
#implementation YourVC
-(void)viewDidLoad{
[super viewDidLoad];
//Fetch Data For both Segments Using Your Web API Call or Local Database API Call
}
-(IBAction)didChangeSegmentIndex:(id)sender{
//When segment is changed you need to tell your collectionView to update the data.
[self.collectionView reloadData];
}
-(NSInteger) collectionView:(UICollectionView *)collectionView numberOfRowsInSection:(NSInteger)section{
//When reloadData is call this method will check that which index is selected.
//And according to selection it will create number of cells.
if (self.segmentControl.selectedIndex == 0){
return arrayOfModelsForFirstCollectionView.count;
}else{
return arrayOfModelsForSecondCollectionView.count;
}
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
//While creating a cell it will check which segment is selected so it will initialize and create the cell with respect to selection of segments.
//Hence you have created two different cells in a single collectionView toggling with a segmentControl.
if (_segment.selectedSegmentIndex == 0) {
return [self makeCellForFirstSegmentAtIndexPath: indexPath];
}
else{
return [self makeCellForSecondSegmentAtIndexPath: indexPath];
}
}
-(UITableViewCell *)makeCellForFirstSegmentAtIndexPath:(NSIndexPath *)indexPath{
//To show data from array you need to use arrayOfModelsForFirstCollectionView because this array is associated with First Segment Index
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"morningcell" forIndexPath:indexPath];
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, cell.frame.size.height,cell.frame.size.width)];
lbl.text = [arrayOfModelsForFirstCollectionView objectAtIndex:indexPath.row];
[cell addSubview:lbl];
[lbl setTextAlignment:UITextAlignmentCenter];
return cell;
}
-(UITableViewCell *)makeCellForSecondSegmentAtIndexPath:(NSIndexPath *)indexPath{
//To show data from array you need to use arrayOfModelsForSecondCollectionView because this array is associated with Second Segment Index
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"eveningcell" forIndexPath:indexPath];
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, cell.frame.size.height,cell.frame.size.width)];
lbl.text = [arrayOfModelsForSecondCollectionView objectAtIndex:indexPath.row];
[cell addSubview:lbl];
[lbl setTextAlignment:UITextAlignmentCenter];
return cell;
}
#end

UICollectionView `reloadData` causes UILabel lines to disappear

I have a UILabel in a collectionView header. The label is set to zero lines, word wrapping, and proper leading/trailing/top space constraints. If i DO NOT call [collectionView reloadData], the label expands properly to text with greater than two lines. Once reloadData is called, the label goes back to a single line...the second line disappears.
- (UICollectionReusableView *) collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
if (kind == UICollectionElementKindSectionHeader) {
header = (viewRollHeader *) [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"header" forIndexPath:indexPath];
header.rollTitle.text = [self.roll objectForKey:#"title"];
header.rollDescription.text = [self.roll objectForKey:#"info"];
[header.cancelButton addTarget:self action:#selector(exit) forControlEvents:UIControlEventTouchUpInside];
return header;
}
return [UICollectionReusableView new];
}
- (CGSize) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section{
if (section == 0) {
CGRect labelRect = [[self.roll objectForKey:#"title"]
boundingRectWithSize: header.rollTitle.frame.size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName : [UIFont fontWithName:#"Arial-BoldMT" size:32.0f]}
context:nil];
return CGSizeMake([[UIScreen mainScreen]bounds].size.width, (174.0f + labelRect.size.height));
}
return CGSizeZero;
}
collectionView:viewForSupplementaryElementOfKind:atIndexPath: is used for the header or footer of the entire collectionView, while collectionView:referenceSizeForHeaderInSection: is used for the header of a specific section of the collectionView
to make sure the size of the header stays consistent and respects your constraints, you'll need to call [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout setHeaderReferenceSize:CGSizeMake(width, height)] once the flowlayout is instantiated
Use label setter property inside delegate method where you configure each cell.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
//add label property here
return cell;
}
After reloading, it calls this method again to configure the label.
Try setting the preferredMaxLayoutWidth to the label
self.label.preferredMaxLayoutWidth = 300;

UICollectionView : two columns

I have a collectionview that will be a menu with two columns, without spacing: so each cell with width 160. And I need a line separating each cell.
Each cell contains an imageview and a label.
I'm trying to add a line separator between the cells like this: How to add views between UICollectionViewCells in a UICollectionView?
But the result is:
Code:
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 0, 0, 0);
}
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return 2;
}
// 2
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return [options count];
}
// 3
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"MenuOptionCell";
UICollectionViewCell *cell = [self.menu dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UIImageView *img = (UIImageView*) [cell viewWithTag:100];
UILabel *lbl = (UILabel*) [cell viewWithTag:90];
UIView *seperatorView = [[UIView alloc] initWithFrame:CGRectMake(cell.frame.origin.x+10, cell.frame.origin.y+cell.frame.size.height, cell.frame.size.width-10, 1)];
seperatorView.backgroundColor = [UIColor whiteColor];
[self.menu addSubview:seperatorView];
[self.menu bringSubviewToFront:seperatorView];
UIView *seperatorView2 = [[UIView alloc] initWithFrame:CGRectMake(cell.frame.origin.x+cell.frame.size.width-10, cell.frame.origin.y, 1, cell.frame.size.height)];
seperatorView.backgroundColor = [UIColor whiteColor];
[self.menu addSubview:seperatorView2];
[self.menu bringSubviewToFront:seperatorView2];
lbl.text = #"TEST";
img.image = [UIImage imageNamed:#"test.png"];
return cell;
}

UICollectionViewCell is overlapped when scrolling

Following code is a very simple program which is used to display numbers from 1-10000 in the UICollectionView. It is displaying correctly without scrolling, but the cells are overlapped if you scroll down and scroll back the collection view.
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 10000;
}
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
return CGSizeMake(100,30);
}
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
NSString *identifier = #"cell_id";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)];
[label setText:[NSString stringWithFormat:#"%d", indexPath.item, nil]];
[cell.contentView addSubview:label];
return cell;
}
The problem here is that the label is being added to the view's cell repeatedly. The old label is not removed when the cell is reused and hence you see multiple numbers overlapped. the solution can be to remove the old label like this
for (UIView *view in cell.contentView.subviews) {
if ([view isKindOfClass:[UILabel class]]) {
[view removeFromSuperview];
}
}
before adding to the cell. However this will create performance problems when the number of subviews increase. You can create a custom cell with a label and then update its value. I haven't tried it but i believe it will work. Hope this helps
This worked for me in Swift 3:
Almost same solution as approved solution, note that I used
cell.subViews
instead of
cell.contentView.subViews
What's worked for me is:
for view in cell.subviews {
view.removeFromSuperview()
}
Set the cell layer anchorPointZ to row index in cellForItemAtIndexPath: method
cell.layer.anchorPointZ = CGFloat(indexPath.row)

UICollectionView insertItemAtIndexPath not working

I am using a UICollectionView to produce a grid of cells say total of 10 i.e. 0-9.
Now, I want to insert a new cell in the grid on click of one of the cells.
so I have added the following line of code [_collectionView insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:10 inSection:0]]]; inside the function didSelectItemAtIndexPath.
So now, if I set indexPathForItem: as 10 (i.e. insert at last) then I get 'Assertion failure' error on this line. If I set `indexPathForItem:' anything between 0-9 then I get 'EXC_BAD_ACCESS...' error on this line.
This is my complete code implementing UICollectionView:
- (void)loadView
{
self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UICollectionViewFlowLayout *layout=[[UICollectionViewFlowLayout alloc] init];
_collectionView=[[UICollectionView alloc] initWithFrame:CGRectMake(0, 97.5, self.view.frame.size.width, self.view.frame.size.height-67.5) collectionViewLayout:layout];
[_collectionView setDataSource:self];
[_collectionView setDelegate:self];
[_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cellIdentifier"];
[_collectionView setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:_collectionView];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection: (NSInteger)section
{
return 35;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
cell.layer.borderWidth=.5f;
cell.layer.borderColor=[UIColor blackColor].CGColor;
if(indexPath.item<31)
{
_dayNumber = [[UILabel alloc] initWithFrame:CGRectMake(30, 30, 15, 15)];
_dayNumber.font = [UIFont systemFontOfSize:12];
_dayNumber.text = [NSString stringWithFormat:#"%ld",(indexPath.item + 1)];
[cell addSubview:_dayNumber];
}
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(self.view.frame.size.width/7, self.view.frame.size.width/7);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex: (NSInteger)section
{
return 0.0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 0.0;
}
// Layout: Set Edges
- (UIEdgeInsets)collectionView:
(UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(0,0,0,0); // top, left, bottom, right
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath: (NSIndexPath *)indexPath
{
[_collectionView insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:0 inSection:0]]];
}
Any help?
Well,
first let's consider this method,
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection: (NSInteger)section
{
return 35; // returning constant value means that you can't add or remove cells later
}
so let me change this to
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection: (NSInteger)section
{
return self.itemsCount;
}
declare itemsCount property in your class interface, like this
#interface YourClass ()
#property (nonatomic) NSInteger itemsCount;
#end
initialize it in loadView or init method,
_itemsCount = 35; // or whatever you want, initial count
now we can insert/delete items, right ? when we call insertItemAtIndexPaths all we have to do is updating actual data before that call, (for example self.itemsCount++, [self.myItems addObject:newItem] )
here is changes in your code
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
self.itemsCount++; // updating data
[_collectionView insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:0 inSection:0]]];
}
One last important thing, in cellForItemAtIndexPath don't alloc init any kind of view and add as subview on cell, this code every time creates UILabels on cell, if you want custom view on cell (like an imageview, button, etc ..) you should subclass UICollectionViewCell and create this stuff in it's init method, here is how it will look like
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
YourCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
cell.layer.borderWidth = 0.5;
cell.layer.borderColor = [UIColor blackColor].CGColor;
cell.dayNumber.font = [UIFont systemFontOfSize:12];
cell.dayNumber.text = [NSString stringWithFormat:#"%d",(indexPath.row + 1)];
return cell;
}
assuming you also changed this line,
[_collectionView registerClass:[YourCell class] forCellWithReuseIdentifier:#"cellIdentifier"];
note that YourCell is a subclass of UICollectionViewCell and has property dayNumber
Apple has a great guide about collection views. I recommend to read it.
Good luck.

Resources