Inserting a New Cell Into A UICollectionView - ios

I’m trying to add in a new cell into my collection view, only if it has more than one item already in it. I have no worked much with collection views, and research in the docs and this site had not helped solve this issue yet. So, on my cellForItemAtIndexPath method, I do a check to see if it is populated. If not, I add the cell, like so:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
if (self.myArray.count != 0) {
return self.myArray.count + 1;
}
else {
return self.myArray.count;
}
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyNormalCollectionViewCellS *cells = (MyNormalCollectionViewCells *) [collectionView dequeueReusableCellWithReuseIdentifier:#"MyNormalCollectionViewCells” forIndexPath:indexPath];
cell.clipsToBounds = NO;
DataClass *data = [self.myArray objectAtIndex:indexPath.row];
[cells configureMyNormalCellsWith:data];
if (0 < self.myArray.count) {
UICollectionViewCell *deleteCell = [UICollectionViewCell new];
deleteCell.backgroundColor = [UIColor yellowColor];
NSArray *newData = [[NSArray alloc] initWithObjects:deleteCell, nil];
[self.myArray addObjectsFromArray:newData];
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
[self.myCollectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
return deleteCell;
}
return cell;
}
For some reason, I have an assertion being thrown, stating:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of items in section 0. The number of items contained in an
existing section after the update (7) must be equal to the number of
items contained in that section before the update (6), plus or minus
the number of items inserted or deleted from that section (0 inserted,
0 deleted) and plus or minus the number of items moved into or out of
that section (0 moved in, 0 moved out).'
Of course, the number usually varies, but it's always angry about that extra cell. Everything is fine up until I try and add it in. Now, being unfamiliar with collection views and after browsing related questions upon this site, I decided it was time to ask the pros.
Does anyone know how I should change this code in order to accomplish what I'm trying to do?

Don't modify data source in collectionView:cellForItemAtIndexPath:. Return different number of items in - collectionView:numberOfItemsInSection: instead:
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
if (self.dataArray.count > 0) {
return self.dataArray.count + 1;
}
else {
return 0;
}
}
In collectionView:cellForItemAtIndexPath: you should return your "normal" cell for "normal" item, and "extra" cell for that extra one, depending on the value of indexPath.row. For example:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row < self.dataArray.count) { // if indexPath.row is within data array bounds
// dequeue, setup and return "normal" cell
} else { // this is your "+1" cell
// dequeue, setup and return "extra" cell
}
}

Related

UITableView Cell Insert Row Glitch

I have a table view with custom cells (all configured in a subclass using auto layout).
The cells load fine, display fine, everything is fine.
The issue is when I am inserting more rows (at the bottom). The table view is representing a feed for posts, so when the user scrolls to the bottom, before reaching the last cell, I load new posts, and then insert them into the table.
When I do this, I get this weird glitchy effect where the cells randomly come down (behind the previous cells) into place, the table view scrolls up a bit, messy.
CODE AT BOTTOM
I've uploaded a clip of me scrolling. When you see the activity indicator,
I stop scrolling. The rest of the movement is from the glitchy behavior.
Is the reason for the glitch because the cells are being drawn with auto-layout?
I would hope not, but idk..I'm not sure what to do regarding a solution. If anyone has any ideas please let me know.
FYI:
I have this (of course, since the cells are all using auto layout)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
I've tried setting the estimated height to an "average" of the expected cell heights, around 65. No difference.
Update
Here's some code:
HomeViewController.m --> viewDidLoad
...
self.tableView = [KATableView.alloc initWithFrame:CGRectZero style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.refreshDelegate = self;
self.tableView.estimatedRowHeight = 75;
self.tableView.rowHeight = UITableViewAutomaticDimension;
[self.view addSubview:self.tableView];
// Constrains to all 4 sides of self.view
[SSLayerEffects constrainView:self.tableView toAllSidesOfView:self.view];
my table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (!self.dataManager.didFinishFetchingData) return 4;
if (self.contentObjects.count == 0) return 1;
if (self.dataManager.moreToLoad) return self.contentObjects.count + 1;
return self.contentObjects.count + 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MYObject *object = self.contentObjects[indexPath.row];
SomeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:object.documentID];
if (!cell) {
cell = [SomeTableViewCell.alloc initWithStyle:UITableViewCellStyleDefault reuseIdentifier:object.documentID];
cell.delegate = self;
} else [cell startListeningForChanges];
return cell;
}
Here is how I am loading more data and adding it to the table view..
- (void)getHomeFeedData:(nullable void(^)(BOOL finished))completed {
[self.dataManager fetchHomeFeedDataForFeedOption:self.homeNavController.feedFilterOption completion:^(NSError * _Nullable error, NSArray<__kindof KAObject *> * _Nullable feedObjects) {
if (error != nil) {
NSLog(#"something went wrong: %#", error.localizedDescription);
if (completed) completed(NO);
return;
}
NSInteger originalCount = self.contentObjects.count;
if (self.dataManager.isFirstTimeLoading) self.contentObjects = feedObjects.mutableCopy;
else {
if (self.dataManager.isGettingNew) for (MYObject *obj in feedObjects) [self.contentObjects insertObject:obj atIndex:0];
else if (feedObjects.count > 0) [self.contentObjects addObjectsFromArray:feedObjects];
}
if (feedObjects.count > 0) {
if (self.dataManager.isFirstTimeLoading) [self.tableView reloadData];
else {
[self.tableView insertCells:feedObjects forSection:0 startingIndex:self.dataManager.isGettingNew? 0 : originalCount];
}
} else if (self.dataManager.isFirstTimeLoading) [self.tableView reloadData];
if (completed) completed(YES);
}];
}
NOTE:
[self.tableView insertCells:feedObjects forSection:0 startingIndex:self.dataManager.isGettingNew? 0 : originalCount];
is simply this:
- (void)insertCells:(nullable NSArray *)cells forSection:(NSInteger)section startingIndex:(NSInteger)start {
if (!cells) return;
NSMutableArray *indexPaths = #[].mutableCopy;
for (id obj in cells) {
NSInteger index = [cells indexOfObject:obj] + start;
[indexPaths addObject:[NSIndexPath indexPathForRow:index inSection:section]];
}
[self insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
}
Update 2
My UITableViewCell subclass content is hidden ATM (too much difficulty in editing all my post content for the purpose of this post). I just have the subviews of each cell set to alpha = 0.f. It's just an image view, some labels, and some buttons.
No constraint issues in console, cells render perfectly when calling [self.tableView reloadData] so maybe there is something I'm doing wrong when inserting the cells?...
When you dealing with UITableView glitches:
Make sure you call UIKit API's on a main thread - turn on Main Thread checker
In your case, there might be an issue that fetchHomeFeedDataForFeedOption:completion: completion block is called not on a main thread.
Your insert is definitely wrong - all delete/insert/update/move calls for UITableView should be wrapped in beginUpdates/endUpdates
Your "load more" component at the bottom might be an issue. You need to address how it's managing contentSize/contentOffset/contentInset of table view. If it does anything but manipulating contentInset - it does wrong job.
While it's hard without debugging the whole solution, I bet options 2 & 3 are the key problems out there.

CollectionView with multiple cell types

I have a collectionView, where each 5th cell must be different than others.
i have write the following code:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
if ((indexPath.row + 1) % 5 == 0) {
return CGSizeMake(screenRect.size.width, screenRect.size.height/5);
}
return CGSizeMake(screenRect.size.width/2, screenRect.size.height/2);
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if ((indexPath.row + 1) % 5 == 0 ) {
NSLog(#"iAdCellWithIndex:%ld", (long)indexPath.row);
CustomCollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CustomCell" forIndexPath:indexPath];
if (!cell)
cell = [[CustomCollectionViewCell alloc] init];
return cell;
}
ImageThumbCell * cell1 = [collectionView dequeueReusableCellWithReuseIdentifier:#"ImageCell" forIndexPath:indexPath];
if (!cell1)
cell1 = [[ImageThumbCell alloc] init];
[cell1 setResultElement:intermediateResults_[indexPath.row]];
return cell1;
}
1-st cycle (first 5 elements) appear well, but then is loading 6-th element I get an exception :
MyApp[1503:888411] -[CustomCollectionViewCell resultElement]: unrecognized selector sent to instance 0x1a9882a0
I dont understand - Why ? the 6-th cell (cell at index 5) must be kind of ImageThumbCell class, not CustomCollectionView class.
can anyone explain this mistake ?
// sorry for bad english. i'm learning, honestly :)
// thanks
The code you posted is calling the setResultElement method, not resultElement. Thus that code shouldn't generate the crash you're seeing.
Your code for creating cells seems reasonable. On every 5th cell you try to dequeue a different type of cell, and if none are available, you alloc/init that other cell type. That makes sense.
My guess is that your bug is somewhere else in your code. Are you trying to read the value of your cell's resultElement property somewhere else?

How to change data of UICollectionView on button click ios?

I am trying to change UICollectionViewCell data on button click(in iPad)?
How to achieve this?
How to send different array count to numberOfItemsInSection on button Click?
In my viewDidLoad:
self.collectionViewImages=[[NSMutableArray alloc]initWithObjects:#"1.jpg",#"2.jpg",#"3.jpg",nil] ;
Then in CollectionViewMethods:
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
return [_collectionViewImages count];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *myCell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell"
forIndexPath:indexPath];
NSString *collectionViewImageName=[self.collectionViewImages objectAtIndex:indexPath.row];
UIImageView * collectionImage = (UIImageView*)[myCell viewWithTag:11];
collectionImage.image =[UIImage imageNamed:collectionViewImageName];
return myCell;
}
Then on ButtonClick--
(As there are 4-5 buttons i am collecting tag of each button in NSInteger and using that property in numberOfItemsInSection like this,
-(void)btnTapped:(id)sender{
int tag= [(UIButton *)sender tag];
NSLog(#"Button Pressed%d",tag);
_buttonIndex=tag;
//_buttonIndex is NSInteger property
//If i add object in NSMutableArray and call reloadData my UI hangs here only.
//If I empty my NSMutableArray and add new objects in it and then call reloadData, I am getting NSRangeException.
}
And now i am using this property in numberOfItemsInSection as...
if (_buttonIndex==1){
[collectionImage setImage:[UIImage imageNamed:[_array1 objectAtIndex:indexPath.row]]];
//My _array1 has 4 objects and my collectionViewImages array has 7 objects.
}
i am getting error message as
*** Terminating app due to uncaught exception 'NSRangeException',
reason: '*** -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 3]'
well you keep two array with a BOOL and based on taps you relload the UicollectionView first keep two array
Declare BOOL
BOOL isChangeData;
Ex :
self.collectionViewImages=[[NSMutableArray alloc]initWithObjects:#"1.jpg",#"2.jpg",#"3.jpg",nil] ;
self.collectionViewImages1=[[NSMutableArray alloc]initWithObjects:#"4.jpg",#"5.jpg",#"6.jpg",nil] ;
isChangeData = TRUE;
First time set original Data as Datasource
-(NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
if(isChangeData){
isChangeData = false;
return [_collectionViewImages1 count];
}
else{
isChangeData = TRUE;
return [_collectionViewImages count];
}
}
in button action Make BOOL true and reload UICOllectionView
-(IBAction)changeData:(id)sender {
[self.CollectionView reloadData];
}
Hope this Works if i did any mistake pls correct me.
I believe you're just simply asking how to add/remove data from a UICollectionView through an IBAction. You would just add an object to your UICollectionView's datasource and reload. Here is an example of adding something:
-(IBAction)addObject:(id)sender {
[self.objectArray addObject:#"new object"];
[self.myCollectionView reloadData];
}

Adding an object before the current index in UICollectionView

I have a simple collection view that has two types of cells. A counter cell, and an "add" cell. My goal is to have the "add" cell remain at the end of the index after new objects are added. When I run the application my "add" cell appears but I get an error when the "add" button is pressed.
Here is the error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of items in section 0. The number of items contained in an
existing section after the update (1) must be equal to the number of
items contained in that section before the update (1), plus or minus
the number of items inserted or deleted from that section (1 inserted,
0 deleted) and plus or minus the number of items moved into or out of
that section (0 moved in, 0 moved out).'
And here is my code:
// retrievedCounters is an NSMutableDictionary
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
// only want one section
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
// get the current count and add one for the "add" cell
return retrievedCounters.count + 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell;
// check whether the index path is at the very end and add the appropriate cell
if (indexPath.row == retrievedCounters.count) {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"AddCounter" forIndexPath:indexPath];
}
else {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CounterCell" forIndexPath:indexPath];
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
[collectionView deselectItemAtIndexPath:indexPath animated:YES];
// insert new counter at index path
[self.collectionView insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:retrievedCounters.count-1 inSection:0]]];
}
Thank you to anyone who can help!
The error indicates a data source issue when changing your datasource. The number of your keys in the mutable dictionary is not correct.
Presumably you are using the NSIndexPaths as keys and the data you need as values. I would think it easier to use an array instead (indexPath.row would then simply point to the right object in the right order).
Check your update method where you change the data in the datasource. You should ensure that your dictionary contains the expected number of items.
In your error message, the 1 item before and after the insert seems to indicate that maybe your dictionary became nil and therefore returns a zero count. Ensure that you have a properly instantiated instance variable.
In your objects array whenever you add new objects
create new array for new objects and than add it like this
[existingArray addObjectsFromArray:newarray];
the sequence would be maintained

cellForItemAtIndexPath being called beyond bounds

I have a UICollectionView which utilizes a custom UICollectionViewCell class. The basic idea is the collection view will traverse directories and display photos contained within the directory (results via API calls). The initial view controller displays a UICollectionView, and taps on the cells generates a new view with a new UICollectionView. The directory contents are stored in an NSMutableDictionary with the directory ID being the key.
The problem occurs when a new view/collectionview is created with a rather short list of photos -- usually less than 30. When I rotate the device, the collection view attempts to re-render, and the app crashes with a "index X beyond bounds [0 .. Y]", with X being a number larger than Y (the last element of the array).
I've noticed this tends to only happen when the newly displayed collection view has less items than the "root" collection view.
Here are examples of the relevant (some of it, anyway) code:
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return [[photos objectForKey:api.directoryID] count];
}
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
PhotoCell *photoCell = [cv dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
photoCell.photo = [[photos objectForKey:api.directoryID] objectAtIndex:indexPath.row];
return photoCell;
}
At the very least, I'd like to do a try/catch on the cellForItemAtIndexPath, but don't know where I would put the try/catch. Perhaps in a custom UICollectionView?
Thanks!
The problem is here:
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return [[photos objectForKey:api.directoryID] count];
}
I assume, you use one delegate and dataSource object for all of your UICollectionView instances.
If so, rewrite your code in this manner:
- (NSInteger)collectionView:(UICollectionView *)view
numberOfItemsInSection:(NSInteger)section
{
if ( view == _firstCollectionView )
{
return [[photos objectForKey:api.directoryID] count];
}
else if ( view == _secondCollectionView )
{
return ...;
}
}
Also you should rewrite collectionView:cellForItemAtIndexPath: method in such way.

Resources