Delete object from Array and UITableView - objective-c

I have a NSMutableArray with a list of prizes.
With those, I filled a UITableView.
Now, I´m trying to delete one of those prizes using TableView CommitEditingStyle. And giving me the "Invalid update: invalid number of sections...." error.
I readed a lot of post over internet about the "Invalid update: invalid number of sections...." but I can´t make it work.
First I remove the object from the array and then update the TableView (like all the posts teach me). But still give me the error
I try setting the array to nill and is the same.
Is necesary to refresh the array or something like this?
This is my Code:
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[[self myPrizesList] removeObjectAtIndex: [indexPath row]];
[self.tableView beginUpdates];
// Either delete some rows within a section (leaving at least one) or the entire section.
if ([[self myPrizesList] count] > 0)
{
// Section is not yet empty, so delete only the current row.
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
}
else
{
// Section is now completely empty, so delete the entire section.
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
}
}
I always said that, for a 5 element array, "the number of sections contained in the table view after the update (5) must be equal to the number of esctions contained in the table view before the update (5), plus or minus the number of sections inserted or deleted (0 insert, 1 deleted)"
If I set the array to nil, say: "the number of sections contained in the table view after the update (1) must be equal to the number of esctions contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (0 insert, 1 deleted)"
Thanks and sorry for my poor english

It is the sections deletion that is the issue, you are deleting the final prize AND section, but when the table reloads it still has the original number of sections... 5. How are you determining the number of sections to return? This needs to reduce if you have deleted one.

Related

Assertion Failure in UICollectionViewData indexPathForItemAtGlobalIndex

I am using performBatchUpdates() to update my collection view, where I am doing a complete refresh, i.e. delete whatever was in it and re-insert everything. The batch updates are done as part of an Observer which is attached to a NSMutableArray (bingDataItems).
cellItems is the array containing items that are or will be inserted into the collection view.
Here is the code:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
cultARunner *_cultARunner = [cultARunner getInstance];
if ( [[_cultARunner bingDataItems] count] ) {
[self.collectionView reloadData];
[[self collectionView] performBatchUpdates: ^{
int itemSize = [cellItems count];
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
// first delete the old stuff
if (itemSize == 0) {
[arrayWithIndexPaths addObject: [NSIndexPath indexPathForRow: 0 inSection: 0]];
}
else {
for( int i = 0; i < cellItems.count; i++ ) {
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
}
[cellItems removeAllObjects];
if(itemSize) {
[self.collectionView deleteItemsAtIndexPaths:arrayWithIndexPaths];
}
// insert the new stuff
arrayWithIndexPaths = [NSMutableArray array];
cellItems = [_cultARunner bingDataItems];
if ([cellItems count] == 0) {
[arrayWithIndexPaths addObject: [NSIndexPath indexPathForRow: 0 inSection: 0]];
}
else {
for( int i = 0; i < [cellItems count]; i++ ) {
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
}
[self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
}
completion:nil];
}
}
I get this error, but not all of the times (why ?)
2012-12-16 13:17:59.789 [16807:19703] *** Assertion failure in -[UICollectionViewData indexPathForItemAtGlobalIndex:], /SourceCache/UIKit_Sim/UIKit-2372/UICollectionViewData.m:442
2012-12-16 13:17:59.790 [16807:19703] DEBUG: request for index path for global index 1342177227 when there are only 53 items in the collection view
I checked the only thread that mentioned the same problem here: UICollectionView Assertion failure, but it is not very clear i.e. doing [collectionview reloadData] is not advisable in the performBatchUpdates() block.
Any suggestions on what might be going wrong here ?
Finally! Ok, here's what was causing this crash for me.
As previously noted, I was creating supplementary views in order to provide custom-styled section headers for my collection view.
The problem is this: it appears that the indexPath of a supplementary view MUST correspond to the indexPath of an extant cell in the collection. If the supplementary view's index path has no corresponding ordinary cell, the application will crash. I believe that the collection view attempts to retrieve information for a supplementary view's cell for some reason during the update procedure. It crashes when it cannot find one.
Hopefully this will solve your problem too!
This is the proper workaround to this crash:
Each of your supplementary views are associated with a certain index path. If you don't have a cell at that index path (initial load, you've deleted the row, etc), return a height of 0 for your supplementary view via your layout's delegate.
So, for a flow layout, implement UICollectionViewDelegateFlowLayout's
(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
method (and the corresponding footer method, if you're using footers) with the following logic
if ( you-have-a-cell-at-the-row-for-this-section )
return myNormalHeaderSize;
else return CGSizeMake( 0,0 );
Hope this helps!
reloadData doesn't work for me, because the whole purpose of using performBatchUpdates is to get the changes animated. If you use reloadData you only refresh the data, but without animations.
So suggestions of "replace performBatchUpdates with reloadData" is pretty much saying "give up on what you're trying to do."
I'm sorry, I'm just frustrated because this error keeps coming up for me while I'm trying to do some great animated updates and my model is 100 % correct, it's some iOS magic inside getting broken and forcing me to change my solutions completely.
My opinion is that Collection Views are still buggy and can't do complicated animated refreshes, even though they should be able to. Because this used to be the same thing for Table Views but those are now pretty stable (it took time, though).
//Edit (Sep 1, 2013)
The reported bug is closed now so this issues seems to have been resolved by Apple already.
I have been having the same problem.
I have tried a number of variations, but the final one that seems to work is [self.collectionView reloadData], where "self.collectionView"is the name of your collection view.
I have tried the following methods, straight from the "UICollectionView Class Reference": inserting, moving, and deleting items.
These were used at first, to "move" the item from one section to another.
deleteItemsAtIndexPaths:
insertItemsAtIndexPaths:
Next, I tried moveItemAtIndexPath:toIndexPath:.
They all produced the following error:
Assertion failure in -[UICollectionViewData indexPathForItemAtGlobalIndex:], /SourceCache/UIKit_Sim/UIKit-2372/UICollectionViewData.m:442
So, try the "reloadData" method.
If you remove the last cell from a section containing header/footer the bug appears.
I tried to return nil for header/footer size/element at that time and this sometimes fixes the issue.
Options:
Reload the whole table view instead of animating the removal of the last item.
Add an additional invisible, basic cell with a size less than 1.
A cheeseball mistake that can lead to this error is reusing the same UICollectionViewFlowLayout on multiple collectionViews on the same viewcontroller! Just init different flowLayouts for each collectionview and you'll be good to go!
I ran into this problem when I delete one of the cells from my collection view.
The problem was that I use a custom layout, and the call layoutAttributesForElementsInRect was returning more than the number of cells in the collection view after the delete.
Apparently UICollectionView just iterates through the array returned by the method without checking the number of cells.
Modifying the method to return the same number of layout attributes solved the crash.
I still couldn't figure out how the global index was incremented so much, but I solved my problem by inserting a temporary item in the underlying datasource array i.e. cellItems and calling [self.collectionview reloadData] in viewDidLoad().
This inserts a placeholder cell temporarily in the collection view until I trigger the actual process using performBatchUpdates().

UITableView multiselect

I have a uitableview with 50 rows populated from a predefined nsarray.
How can I select multiple the rows with say maximum 3 allowed at a time and show check when selected and remove check when deselected/
I am really new to xcode and any help is much appreciated. Thank you.
Your data needs to keep track of whether it is selected or not.
Two common ways are: each object in your predefined array has a BOOL that indicates whether or not it is selected, or you keep a second array that holds only references to selected objects. Since you're limited to three selected, the second option might be better.
When someone selects a cell in your table, you change the selection status of the related object, either switching its BOOL or adding/removing it in the extra array. This is also the place to check whether you already have as many selections as you allow. If selections have changed, you then tell your table to reload data.
In cellForRowAtIndexPath: you check whether or not the object is selected and mark it accordingly.
int counter = 0; //keep track of how many rows are selected
int maxNum = 3; //Most cells allowed to be selected
//Called when the user selects a row
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//If the cell isn't checked and there aren't the maximum allowed selected yet
if (cell.accessoryType != UITableViewCellAccessoryCheckmark && counter < maxNum)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
counter++;
}
else if (counter >= maxNum) return; //Don't do anything if the cell isn't checked and the maximum has been reached
else //If cell is checked and gets selected again, deselect it
{
cell.accessoryType = UITableViewCellAccessoryNone;
counter--;
}
}
You might also want to keep an array of indices of the cells that are selected, in case you want to do something with the data that's in them. If you don't know how to do this, let me know and I'll add the code.
Notes:
You need to be implementing the table view delegate protocol in order to have this method called correctly.
This isn't the "best" way to do it (using cell content to keep track of selection is generally frowned up) but it is very easy.
You might run into problems with cell reuse. If you want to fix that, store the cell's index and set the accessory type in cellForRowAtIndexPath

Objective-C UITableView index

How do I read always the most upper line (cell.textLabel.text) in a UITableView?
I have two slightly different approaches;
1:
UITableViewCell *cell2 = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellText2 = cell2.textLabel.text;
NSLog (#" cellText2 = %#", cellText2);
2:
NSMutableString *newidea = [self.array objectAtIndex:indexPath.row];
NSLog (#"newidea = %#", newidea);
Both codes are inside the method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
self.array is same array that fills up the tableView.
The former approach always shows text from the sixth cell. The latter approach always shows text from the fifth cell. In my tableview there is four cells at a time.#
What I want to get is the text from the most upper cell.
I think you have the wrong approach. You don't read values from cells, instead you let the cells read values from your data array. A cell can always have an arbitrary value since they are reused. Even if you have 30 "cells" in your table view there may only be 5 existing actual cells. When a cell goes outside the table view when you scroll, it is moved to the bottom and reused as the next cell. That's why you always have to set the values for each cell on the index path.
Instead you should get the value in the first cell from your data array if you have one. When the table view asks what title the cell att indexPath.row == 0 should have, you give it to it in cellForRowAtIndexPath, for example from an array called "_cellTitles" containing 30 strings for 30 different cells.
If you want to get the text from the "most upper" visible cell, then you can call indexPathsForVisibleRows on the table view. The first object in the returned array is the index path for the most upper visible cell. You can check the string in your array at index indexPath.row.
Example:
NSArray *visibleRows = [self.tableView indexPathsForVisibleRows];
NSIndexPath *firstVisibleCell = [visibleRows objectAtIndex:0];
NSString *firstVisibleCellTitle = [_myDataArray objectAtIndex:indexPath.row];
If you always want to read from first row, instead of indexPath just say 1 there. That way it will always read from the first row.

inserting UITableViewCell not working

Could somebody explain what I'm doing wrong in attempting to insert a new UITableViewCell? I'm trying to insert a custom UITableViewCell, but it throws the following error: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (section == 0)
return 1;
else if (section == 1)
return numberOfRows;
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"EditableCell";
EditableCell *editableCell = (EditableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (editableCell == nil) {
editableCell = [[EditableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
_myTextField = [editableCell textFieldCell];
if (indexPath.section == 0){
[_myTextField setPlaceholder:#"Menu Name"];
[_myTextField setReturnKeyType:UIReturnKeyNext];
}
else if (indexPath.section == 1){
[_myTextField setPlaceholder:#"Category"];
[_myTextField setReturnKeyType:UIReturnKeyNext];
}
else {
[_myTextField setPlaceholder:#"Category"];
[_myTextField setReturnKeyType:UIReturnKeyDone];
}
_myTextField.keyboardType = UIKeyboardTypeDefault;
_myTextField.delegate = self;
return editableCell;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
if (textField.returnKeyType == UIReturnKeyNext) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
NSArray *array = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView insertRowsAtIndexPaths:array withRowAnimation:UITableViewRowAnimationTop];
numberOfRows = numberOfRows + 1;
[self.tableView reloadData];
}
}
You need to make sure that the number of rows returned in the method below now returns the correct number of rows.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
I am sure what is happening is that you are returning the same value here after your insert, whereas the runtime is expecting it to be one more than it was before you made the call to insert a row.
In a bit more detail, what is happening in MVC terms (Model/View/Controller) is this:
Your call to insertRowsAtIndexPaths is requesting that the Controller update the View with an additional row.
The Controller then does a sanity check to see if you have your sums right, so it calls:
tableView:numberOfRowsInSection
Now the controller knows what value this method returned last time (in your case, 1), and it also knows that you have requested to insert a row. There have been no delete calls, so it expects the next time it calls this method, the value should be (last time's value + rows inserted - rows deleted) = 2
If the Controller deems that you have things in order, it will then call cellForRowAtIndexPath (along with other methods) to get the actual cells for each of the rows in each section.
So for your problem, you need to keep track of the rows in your model - maybe in an array or an ivar with the count of rows available. Update your model by updaing this value as you add/delete rows before you make the call to insertRowsAtIndexPath, and then return this value when tableView:numberOfRowsInSection is called.
Additional tip:
If you wish to have your cell inserts/deletes to be animated, change your code to the below. Notice there is no call to reloadData anymore, instead the insert/reload is wrapped in begin/end update calls - The animated updates will occur after endUpdates due to the reloadSections call.
NSMutableIndexSet *sectionsToReload = [[NSMutableIndexSet alloc] init];
[sectionsToReload addIndex:1]; // Add sections as necessary
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:array withRowAnimation:UITableViewRowAnimationTop];
[self.tableView reloadSections:sectionsToReload withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
Check out the WWDC 2010 video from Apple, "Mastering Table Views" - explains everything really nicely, regarding using insertRowsAtIndexPaths and associated methods.
HTH,
Stretch
After you attempt to insert a new cell, your datasource must contain the number of original cells (1) plus the "extra" cell you are inserting (1) for a total of 2. So your tableView:numberOfRowsInSection: must return 2.
The error message indicated that tableView:numberOfRowsInSection: returned 1.
Update your datasource so that it returns this "extra" cell.

NSFetchedResultsControllerDelegate and deleteRowsAtIndexPaths: Section issue

In response of a delete operation on a CELL (not a section) of a tableView connected to a NSFetchedResultsController i obtain this error :
'Invalid update: invalid number of
sections. The number of sections
contained in the table view after the
update (1) must be equal to the number
of sections contained in the table
view before the update (2), plus or
minus the number of sections inserted
or deleted (0 inserted, 0 deleted).'
I understand that the problem is related to Section numbers after and before update. It says that i didn't delete sections, but the after-value is different from the before-value.
Ok this's true! but my section depend on row cells, so if i remove the last row cell of a section, the section disappear.
Here how i define Section and Row numbers:
Sections are created to group row by an attribute "date". Thus, if a row has the attribute "date" 10 April 2010 and a second row has 11 April 2010 i have 2 sections containing 1 row.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.controller sections]count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
And here my definition of commitEditingStyle, where i remove cell from table and delete data from DB (object type "Transactions" is a subclass of NSManagedObjectContext, that define my model).
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
Transactions *trans = (Transactions *)[self.controller objectAtIndexPath:indexPath];
//Delete transaction
[self.context deleteObject:trans];
NSError *error = nil;
[self.context save:&error];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
}
I find where's the problem.
The delegate is defined in a controller called from a UITabBar. Every time its view appears, a function is called to Fetch data on NSFEtchedResultController(with performFetch) and setup the delegate for the NSFetchedResultControllerDelegate. This way (i don't know exactly why) every time i try to delete or insert cell/section on table, the delegate seems to be duplicated as many time as i opened the controller via UITabBar, thus Delegate function are repeated more than one time creating problem and errors that i described in the question.
To avoid this problem, now i setup Request and Delegate in init function of ViewController and in no other places. This ensure that it set only one reference to delegate and only one Fetch is performed over NSFetchResultController.