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.
Related
Hi I am very new for Ios and in my project I am using UICollectionView ok that's fine.
But here my main requirement is that I want to load the UICollectionView cells dynamically when we are scrolling the collectionView.
I mean when we launch the app then first "5" records need to load and when we are scrolling for the first time next "5" records need to load and when we scrolling for the second time the next "5" records need to be displayed and in that way all records need to load.
Please help me.
How can I do this?
my code:-
#import "ViewController.h"
#interface ViewController ()
{
UICollectionView * _collectionView;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UICollectionViewFlowLayout *layout=[[UICollectionViewFlowLayout alloc] init];
_collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, 320, 480) collectionViewLayout:layout];
[_collectionView setDataSource:self];
[_collectionView setDelegate:self];
[_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cellIdentifier"];
[_collectionView setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:_collectionView];
UIView *refreshView = [[UIView alloc] initWithFrame:CGRectMake(0, _collectionView.frame.size.height - 50, 0, 0)];
[_collectionView addSubview:refreshView];
UIRefreshControl *refreshControl = [UIRefreshControl new];
refreshControl.tintColor = [UIColor redColor];
[refreshControl addTarget:self action:#selector(viewDidBeginRefreshing:) forControlEvents:UIControlEventValueChanged];
[refreshView addSubview:refreshControl];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 5;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
for (id subview in cell.contentView.subviews) {
if ([subview isKindOfClass:[UIImageView class]]) {
[subview removeFromSuperview];
} else if ([subview isKindOfClass:[UILabel class]]) {
[subview removeFromSuperview];
}
}
cell.backgroundColor = [UIColor lightGrayColor];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
return CGSizeMake(70, 70);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
UIEdgeInsets insets=UIEdgeInsetsMake(10, 10, 10, 10);
return insets;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 30.0;
}
Solution: You can add infinite scrolling to your existing UICollectionView use the following open source library to archive your requirement.
UIScrollView-InfiniteScroll
Solution 1: If you want to do infinite scrolling in a manner more like a desktop web app, you should implement the UIScrollViewDelegate protocol (or UICollectionViewDelegate, which is a superset) and listen for the scrollViewDidScroll: callback.
Within that callback, iterate through the UICollectionView's indexPathsForVisibleItems and check to see if any of the index paths map to the 'last' item in your collection, which indicates the user has scrolled to the end.
At that point, call your logic to load more stuff.
Solution 2:
In table view, you should load more data in willDisplayCell() method. With collections, there is no such a counterpart. But, you can do like this. Hope, it helps.
(void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
// Other code here ...
if (indexPath.item == [self.data count] - 1) {
[self loadMoreData];
}
}
May be it will help you.
I'm trying to create a week calendar by using uiclloectionview. here is what it would look like when the view come to focus:
original view
when I scroll to bottom and scroll back to top. those cells that were disappeared because of scrolling change text.
scrolled view
Here is my code:
#interface ViewController ()
#end
#implementation ViewController
#synthesize calendar;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//[self.calendar setCollectionViewLayout:[[CalendarFlowLayout alloc]init]];
timeline = [NSArray arrayWithObjects:#"8:00", #"9:00",#"10:00",#"11:00",#"12:00",#"13:00",#"14:00",#"15:00",#"16:00",#"17:00",#"18:00",#"19:00",#"20:00",#"21:00",#"22:00",nil];
weekline=[NSArray arrayWithObjects:#"Mon", #"Tue",#"Wen",#"Thr",#"Fri",#"Sat",#"Sun",nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger) numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 128;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
if (![cell.contentView viewWithTag:2]) {
UILabel * title;
UIView *message;
title =[[UILabel alloc]initWithFrame:CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height)];
title.font=[UIFont systemFontOfSize:14.0];
title.textAlignment=NSTextAlignmentCenter;
//title.backgroundColor=[UIColor redColor];
if (indexPath.row%8==0) {
int time =indexPath.row/8;
if (time<[timeline count]) {
title.text =[NSString stringWithFormat:#"%#",[timeline objectAtIndex:time]];
}
}else if(indexPath.row>0&&indexPath.row<8){
if (indexPath.row<[weekline count]+1) {
title.text =[NSString stringWithFormat:#"%#",[weekline objectAtIndex:indexPath.row-1]];
}
[cell.layer setBorderColor:[UIColor blackColor].CGColor];
[cell.layer setBorderWidth:1.0f];
}else{
[cell.layer setBorderColor:[UIColor blackColor].CGColor];
[cell.layer setBorderWidth:1.0f];
}
title.textColor=[UIColor blackColor];
message = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, cell.frame.size.width, cell.frame.size.height)];
message.tag = 2;
message.backgroundColor =[UIColor clearColor];
[message addSubview:title];
[cell.contentView addSubview:message];
}
return cell;
}
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
CGFloat width =[[UIScreen mainScreen]bounds].size.width/8;
return CGSizeMake(width, width);
}
-(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
return UIEdgeInsetsMake(0, 0, 0, 0);
}
-(CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section{
return 0.0;
}
-(CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{
return 0.0;
}
#end
I just wonder why indexpath keep changing and how to fix this issue? thank you.
You only configure the cell if it doesn't have the message UIView (tag 2) - so when cells are reused you are simply returning them as they were configured previously - however there is no guarantee that the cell be reused in the same indexPath as it was previously - in fact you can pretty much guarantee that it won't.
You need to correctly configure your cell every time. This will be much easier if you configure your prototype cell in the storyboard, or if you don't want to do that create a UICollectionViewCell subclass that adds the title and message controls in its init method.
I want to create 3 cells in each row, with width & height 104.0f each. But I am getting the output as shown in the screenshot.
The code used is given below. Since I am new to UICollectionView, I have no idea how to implement this. Where do I design the cells incase if frames are different for all?
- (void)viewDidLoad
{
....
UICollectionViewFlowLayout *layout=[[UICollectionViewFlowLayout alloc] init];
dealListCollectionView=[[UICollectionView alloc] initWithFrame:CGRectMake(1.0f, 1.0f, 320.0f, 525.0f) collectionViewLayout:layout];
[dealListCollectionView setDataSource:self];
[dealListCollectionView setDelegate:self];
[dealListCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cellIdentifier"];
[dealListCollectionView setBackgroundColor:[UIColor lightGrayColor]];
[self.view addSubview:dealListCollectionView];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 10;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
cell.backgroundColor=[UIColor greenColor];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(104, 104);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 0, 0, 0);
}
- (UIEdgeInsets)insetsForItemAtIndexPath:(NSIndexPath *)indexPath {
return UIEdgeInsetsMake(2, 2, 2, 2);
}
Click on your CollectionView, Go to the Size Inspector and change the default value of Min Spacing For Cells and For Rows. For your size 104x104 it have to be 4px.
The below code solved my problem :
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 2.0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 2.0;
}
I have a Collection View and has custom cells with images and labels in there. I have set my collection view as follows -
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.minimumLineSpacing = 150.0f;
flowLayout.minimumInteritemSpacing = 104.0f;
flowLayout.sectionInset = UIEdgeInsetsMake(20, 20, 100, 120);
_archiveCollectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:flowLayout];
_archiveCollectionView.frame = CGRectMake(30, 218, _archiveCollectionView.frame.size.width - 60, _archiveCollectionView.frame.size.height - 350);
_archiveCollectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_archiveCollectionView.backgroundColor = [UIColor clearColor];
_archiveCollectionView.delegate = self;
_archiveCollectionView.dataSource = self;
[self.archiveCollectionView registerNib:[UINib nibWithNibName:#"FullArchiveEditionCell" bundle:nil] forCellWithReuseIdentifier:#"MyCell"];
[_archiveCollectionView reloadData];
[self.view addSubview:_archiveCollectionView];
I have also set the following methods:
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _chosenCategoryArray.count;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self addEditionsChildView];
}
-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
However, my didSelectItemAtIndexPath never gets called when I select a cell. Any help please?
I had a similar problem and it turned out I was using the same cell reuse identifier in two different collection views
In your header file have you implemented UICollectionViewDelegate as like below,
#interface HAViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate>
I have the same problem. And I solved by using storyboard to locate the collection cell in the collection view controller. Then tick User InterAction Enabled. I think using code to set in the UICollectionViewCell would also work. Hope it would help.
Try with changing sequence as given below
[_archiveCollectionView reloadData];
[self.view addSubview:_archiveCollectionView];
to
[self.view addSubview:_archiveCollectionView];
[_archiveCollectionView reloadData];
Implement the below delegate method.
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
return YES;
}
And implement your
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
//your selection management code
}
and
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
//deselection handling code
}
This is the first time I have ever tried to make a UICollectionView before, I have an ImageArray that is being read from coreData.. all of the images are NSData I am reading them into a UIImage... I would then like to display the UIImage into a UICollectionView that I allow the user to select from to update a preview view.
I have added these 3 delegates to my class.
And these are the delegates I have implemented.
// add collectionView
photoCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(10.0, 50.0, 200.0, 700.0)];
[photoCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"PhotoCell"];
photoCollectionView.dataSource = self;
photoCollectionView.delegate = self;
[self.view addSubview:photoCollectionView];
//..
#pragma mark - CollectionView Delegates
#pragma mark -- UICollectionView Datasource
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return [imageArray count];
}
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"PhotoCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
#pragma mark -- UICollectionView Delegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// TODO: Select Item
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
// TODO: Deselect item
}
#pragma mark –- UICollectionViewDelegate FlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *currentPhotoDict = [imageArray objectAtIndex:indexPath.row];
UIImage *imageForCollection = [UIImage imageWithData:[currentPhotoDict objectForKey:#"DImage"]];
//show image in collectionview?
}
- (UIEdgeInsets)collectionView:
(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(50, 20, 50, 20);
}
When I run this code above I am receiving the following error.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'
The error is stating that you need to create the collection view with a non-nil layout object.
You need to use...
photoCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(10.0, 50.0, 200.0, 700.0) collectionViewLayout:someLayoutObject];
You need to create the layout object first too.
Possibly just use...
photoCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(10.0, 50.0, 200.0, 700.0) collectionViewLayout:[[UICollectionViewFlowLayout alloc] init]];
You need to specify kind of layout you want to use when you initialise collection view:
UICollectionViewFlowLayout *layout=[[UICollectionViewFlowLayout alloc] init];
photoCollectionView =[[UICollectionView alloc] initWithFrame:CGRectMake(10.0, 50.0, 200.0, 700.0) collectionViewLayout:layout];