iOS - Error when adding row to UITable (Objective-C) - ios

I'm getting this error when adding a row to a UITable.
'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 7. The number of rows contained in an existing section after the update (0) 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 (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
number of elements:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([self tableView:tableView canCollapseSection:section])
{
if ([expandedSections containsIndex:section])
{
return mArray[section];
}
return 1; // only top row showing
}
// Return the number of rows in the section.
return 1;
}
Counting number to be in each part:
int k = 0;
for (int i = 0; i < 28; i++) {
if ([[currentEntry objectForKey:#"item"] isEqualToString:items[i]]) {
mArray[i] += 1;
k = i;
}
}
Adding to table
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self tableView:tableView canCollapseSection:indexPath.section])
{
if (!indexPath.row)
{
// only first row toggles exapand/collapse
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSInteger section = indexPath.section;
BOOL currentlyExpanded = [expandedSections containsIndex:section];
NSInteger rows;
NSMutableArray *tmpArray = [NSMutableArray array];
if (currentlyExpanded)
{
rows = [self tableView:tableView numberOfRowsInSection:section];
[expandedSections removeIndex:section];
}
else
{
[expandedSections addIndex:section];
rows = [self tableView:tableView numberOfRowsInSection:section];
NSLog(#"SSection:%d", rows);
}
for (int i=1; i<rows; i++)
{
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i
inSection:section];
[tmpArray addObject:tmpIndexPath];
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (currentlyExpanded)
{
dispatch_async(dispatch_get_main_queue(), ^{
[tableView deleteRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationTop];
});
}
Error occurs here
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[tableView insertRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationTop];
});
}
}
}
}
This error only occurs in one section which is 7. All other sections are fine

First you need add object in collection which telling tableview about count of items for appearing, create index path for new cell and make insert row with index path. Perhaps you create wrong index path.

I think your array and your numberOfRows should be matching in your 7th section.
//MARK: UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 7) {
return YOUR_ARRAY.count;
}
}

Related

Expand/Collapse table row when multiple number of sections in tableview

Here i am trying to create expand/collapse table row.it is working fine with this code in didSelectRowAtIndexPath only for 1 section:
if (selectedIndex == indexPath.row) {
selectedIndex = -1;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil]withRowAnimation:UITableViewRowAnimationFade];
return;
}
//for table view collapse
if (selectedIndex != -1) {
NSIndexPath *prePath = [NSIndexPath indexPathForRow:selectedIndex inSection:0];
selectedIndex = (int)indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:prePath, nil]withRowAnimation:UITableViewRowAnimationFade];
}
//for non selection
selectedIndex = (int)indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil]withRowAnimation:UITableViewRowAnimationFade];
through this code i can expand and collapse table row but if and only if for 1 section in table but when multiple section comes it is expanding each sections particular row.so when i click on section 0's 1st row it is going to open all section's 1st row.
How to get rid of this situation?
Tapping on particular section button you can change number of rows.
In section view you have added one button for all section which has common method while tapping.
In tapping method you have to just change bool variable value for particular section
ex.. if sender tag you initials with section number.
So For Section 0 button is taped then in tapping method
if sender.tag == 0
{
if section0tag
{
section0tag = false
}
else
{
section0tag = true
}
table.reload() ..//Which call number of rows at section data source method
}
For number of rowsatSection method data source method
if section == 0
{
if section0tag
{
return 5 //Expanding rows
}
else
{
return 0 //collapsing rows
}
}
Try this method just reload tableview (i.e rOne,rTwo, etc are the bool value):
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return rOne ? oneRoundType.count : 0 ;
}else if (section ==1) {
return rTwo ? twoRoundType.count : 0 ;
}else if (section ==2) {
return rThree ? threeRoundType.count : 0 ;
}else if (section ==3) {
return rFour ? fourRoundType.count : 0 ;
}else if (section ==4) {
return rFive ? fiveRoundType.count : 0 ;
}
return 0;
}
Please try below code:
Global declaration of sectionIndexToBeExpanded as int to check whether it is expanded or not
int sectionIndexToBeExpanded;
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//Assign value 100 to sectionIndexToBeExpanded, if you do not want row expanded
sectionIndexToBeExpanded = 100;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [yourArray count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (sectionIndexToBeExpanded==100) //If not expanded
{
return 1;
}
else if (sectionIndexToBeExpanded==section) //If section expand add one more row
{
return 2;
}
else
return 1;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row==0)
{
if (sectionIndexToBeExpanded==100) //If section not expanded set value of sectionIndexToBeExpanded to 100
{
UITableViewCell *cell=(UITableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:indexPath.row+1 inSection:indexPath.section]] withRowAnimation:UITableViewRowAnimationRight];
sectionIndexToBeExpanded=(int)indexPath.section;
[tableView endUpdates];
}
else if (sectionIndexToBeExpanded==indexPath.section) // If Section is already expanded at indexpath then collapse ie. delete row
{
UITableViewCell *cell=(UITableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:indexPath.row+1 inSection:sectionIndexToBeExpanded]] withRowAnimation:UITableViewRowAnimationLeft];
sectionIndexToBeExpanded=100; //Set section to not expanded
[tableView endUpdates];
}
else //Expand section
{
UITableViewCell *cell=(UITableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:indexPath.row+1 inSection:sectionIndexToBeExpanded]] withRowAnimation:UITableViewRowAnimationLeft];
//set value of expanded section to sectionIndexToBeExpanded
sectionIndexToBeExpanded=(int)indexPath.section;
[tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:indexPath.row+1 inSection:indexPath.section]] withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
}
}
}
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}

How to manipulate array in section?

I have a single array of any count. I want to divide in different section as multiple of 7. I am unable to get this to work. Here is a sample of 2 elements.
- (void)viewDidLoad {
[super viewDidLoad];
array =[NSMutableArray arrayWithObjects:#"d",#"s",#"a",#"qq",#"dqd",#"dqq",#"qdqdf",#"dqdfqf", nil];
// Do any additional setup after loading the view from its nib.
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return ceil(array.count / 2.0); // round up the floating point division
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger sections = [self numberOfSectionsInTableView:tableView];
if (section == sections - 1) {
NSInteger count = array.count & 2;
if (count == 0) {
count = 2;
}
return count;
} else {
return 2;
}
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
// cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
Your question isn't clear but I think you want 7 rows in every section except the last section which would have just enough for the last remaining rows that don't fit in the rest of the sections.
Assuming this is correct you need to properly calculate the number of sections as follows:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return ceil(array.count / 7.0); // round up the floating point division
}
Now the number of rows in each section will be 7 except for the last section which could have 1 - 7.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger sections = [self numberOfSectionsInTableView:tableView];
if (section == sections - 1) {
NSInteger count = array.count % 7;
if (count == 0) {
count = 7;
}
return count;
} else {
return 7;
}
}
You also need to be able to convert an indexPath into an array index:
NSInteger index = indexPath.section * 7 + indexPath.row;
And you need to be able to convert an array index into an indexPath:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index % 7 inSection:index / 7];
Or, instead of all of this, you could setup your data structures to be an array of arrays. That actually makes your data better match how it will be used by the table.
Update for you revised question:
Your cellForRowAtIndexPath method needs to change:
//cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
to:
NSInteger index = indexPath.section * 7 + indexPath.row;
cell.textLabel.text = tableData[index];
just like I showed above.
So I'm not entirely on point with objective-C as it's been awhile.
But the easiest thing I'd think to do would be a for loop that iterates through the length of the array and at every 7th will divide the array.
Here's a bit of psuedocode.
for(int i =0; i<array.length<i=i+7)
{
//take the first index, take the 7th index.
//split the array from the first index to the 7th
//repeat for all remaining values.
}
I'm not sure if you wanted all the different sections that it could make from the 7 intervals, or only one. If you could clarify I might be able to answer this better.

Unable to add and delete rows simultaneously

I am following a tutorial,where I can expand the table view by adding some sub-cells and collapse the table by removing the sub-cells. I am trying to change how the expand operation should execute. When I tap on a row,it expand and shows the sub-cells,and when I tap on other row,the previous expanded row should close. I am not able to do this . I tried the following but I couldnt make the one row expandable at a time,and the other row should close when one expands.
Note: This code works fine,but to collapse the row,we need to tap twice on the same row. I am trying to collapse when other parent is tapped.
Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Node *node = [self nodeForIndexPath:indexPath];
// differenet table view cell prototypes for different node levels
UITableViewCell *cell;
if(node.level == 0)
{
cell = [tableView dequeueReusableCellWithIdentifier:#"level1cell" forIndexPath:indexPath];
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:#"level2cell" forIndexPath:indexPath];
}
// set the nodes title as row text
cell.textLabel.text = node.title;
// attach the nodeId for later lookup when selected
cell.tag = node.nodeId;
// Configure the cell...
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Node *node = [self nodeForIndexPath:indexPath];
// NSLog(#"node id is %ld level is %ld and indexpath.row is %d",(long)node.nodeId,(long)node.level,indexPath.row);
node.expanded = !node.expanded;
if (node.level==0) {
NSLog(#"you tapped parent");
//now check other parents are expanded or not
}else{
NSLog(#"you tapped child");
}
//insertion always happen after deletion
// NSLog(#"you touched at %#,index row is %d",indexPath,indexPath.row);
if(node.expanded )
{
// add n rows
NSMutableArray *indexPaths = [NSMutableArray array];
for(NSInteger i=indexPath.row; i< indexPath.row+node.subNodes.count; i++)
{
[indexPaths addObject:[NSIndexPath indexPathForRow:i+1 inSection:0]];
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
else
{
//you store the node refe after row is expanded
// delete n rows
NSMutableArray *indexPaths = [NSMutableArray array];
for(NSInteger i=indexPath.row; i< indexPath.row+node.subNodes.count; i++)
{
[indexPaths addObject:[NSIndexPath indexPathForRow:i+1 inSection:0]];
}
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
}
#pragma mark - Private helper
-(Node*) nodeForIndexPath:(NSIndexPath*)indexPath
{
int idx = 0;
for(Node *node in _nodes)
{
if(idx == indexPath.row)
{
return node;
}
idx++;
if(node.expanded)
{
for (Node *subNode in node.subNodes)
{
if(idx == indexPath.row)
{
return subNode;
}
idx++;
}
}
}
return nil;
}
KMAccordionTableViewController can helps you?
https://github.com/klevison/KMAccordionTableViewController
Hm... If your current code is working, but you need to tap twice to collapse the open/selected row, perhaps it's because didDeselectRowAtIndexPath: is being called during that first tap in place of didSelectRowAtIndexPath: in order to deselect the selected row. I'd recommend configuring your tableview to allow for multiple selection so that didSelectRowAtIndexPath: is called every time, ex:
- (void)viewDidLoad {
[super viewDidLoad];
tableView.allowsMultipleSelection = YES;
}

Rows not being inserted into UITable

I have an app with a table view that expands/collapses sections, following the example in Apple's Table View Animations & Gestures sample app. I am running into problems when an item is added to a closed section: after that, the section no longer opens, and I get an exception when I try to open and then close it.
I've traced this to some strange behaviour in the open/close methods:
-(void)sectionHeaderView:(SectionHeaderView*)sectionHeaderView sectionOpened:(NSInteger)section {
if (![[sectionHeaderArray objectAtIndex:section] isOpen]) {
[[sectionHeaderArray objectAtIndex:section] setIsOpen:YES];
NSLog(#"self.tableView: %#", self.tableView);
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
NSInteger countOfRowsToInsert = [sectionInfo numberOfObjects];
NSMutableArray *indexPathsToInsert = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < countOfRowsToInsert; i++) {
[indexPathsToInsert addObject:[NSIndexPath indexPathForRow:i inSection:section]];
}
// Apply the updates.
[self.tableView beginUpdates];
NSLog(#"Count of rows to insert: %d", [indexPathsToInsert count]);
NSLog(#"Rows before insert: %d", [self.tableView numberOfRowsInSection:section]);
[self.tableView insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:UITableViewRowAnimationTop];
NSLog(#"Rows after insert: %d", [self.tableView numberOfRowsInSection:section]);
[self.tableView endUpdates];
}
}
-(void)sectionHeaderView:(SectionHeaderView*)sectionHeaderView sectionClosed:(NSInteger)section {
if ([[sectionHeaderArray objectAtIndex:section] isOpen]) {
[[sectionHeaderArray objectAtIndex:section] setIsOpen:NO];
NSInteger countOfRowsToDelete = [self.tableView numberOfRowsInSection:section];
if (countOfRowsToDelete > 0) {
NSMutableArray *indexPathsToDelete = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < countOfRowsToDelete; i++) {
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:section]];
}
[self.tableView beginUpdates];
NSLog(#"Count of rows to delete: %d", [indexPathsToDelete count]);
NSLog(#"Rows before delete: %d", [self.tableView numberOfRowsInSection:section]);
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:UITableViewRowAnimationTop];
NSLog(#"Rows after delete: %d", [self.tableView numberOfRowsInSection:section]);
}
[self.tableView endUpdates];
}
}
The log messages show that, on open (insert rows), >0 rows are being inserted, and yet the row count for that section stays 0:
2012-03-31 13:36:17.454 QuickList7[5523:fb03] Count of rows to insert: 3
2012-03-31 13:36:17.454 QuickList7[5523:fb03] Rows before insert: 0
2012-03-31 13:36:17.454 QuickList7[5523:fb03] Rows after insert: 0
This sets up an inconsistent state between the table and data source, and then when I try to "collapse" the section, I get the following exception:
2012-03-31 13:48:35.783 QuickList7[5523:fb03] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid table view update. The application has requested an update to the table view that is inconsistent with the state provided by the data source.'
How can I insert 3 rows, and still end up with 0 rows?
Thanks,
Sasha
I found the problem! It was actually in the fetchedResultsController's change handler. It was responding to changes to closed sections, which left the table in a bad state, and out of sync with the data source. So I added a check for each update to only insert/delete/update rows if the containing section is open.
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tv = self.tView;
switch(type) {
case NSFetchedResultsChangeInsert:
if ([[sectionHeaderArray objectAtIndex:newIndexPath.section] isOpen]) {
[tv insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
}
break;
case NSFetchedResultsChangeDelete:
if ([[sectionHeaderArray objectAtIndex:indexPath.section] isOpen]) {
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
break;
case NSFetchedResultsChangeUpdate:
if ([[sectionHeaderArray objectAtIndex:indexPath.section] isOpen]) {
[self configureCell:[tv cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
}
break;
case NSFetchedResultsChangeMove:
if ([[sectionHeaderArray objectAtIndex:indexPath.section] isOpen]) {
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
if ([[sectionHeaderArray objectAtIndex:newIndexPath.section] isOpen]) {
[tv insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
}
break;
}
}
In my app I've implemented a similar behavior in a very different way because I was running into this type of problem a lot.
I have a table with MenuNameCells, MenuItemCells and a static cell at the bottom. Only one menu is expanded at a time, and tapping a MenuNameCell expands or collapses that menu. Since I keep the MenuNameCell in its own section and the MenuItemCells in another, I only have to insert/delete entire sections when I reload the table.
Here's my table's data source:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// number of menus, plus 1 if a menu is open, plus 1 static cell
return [self.restaurant.menus count]+(self.menu != nil ? 1 : 0)+1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// if this section is our selected menu, return number of items, otherwise return 1
int numberOfRowsInSection = ([self indexPathIsInMenuItemSection:section] ? [[self.menu items] count] : 1);
return numberOfRowsInSection;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == [tableView numberOfSections]-1) {
// ... set up and return static cell
}
if ([self indexPathIsInMenuItemSection:indexPath.section]) {
// ... set up and return menu item cell
} else {
// ... set up and return menu name cell
}
}
and my table's delegate:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// return if it's a static cell
if (indexPath.section==[tableView numberOfSections]-1)
return;
// if it's a menu name cell, close open menu and maybe expand this menu
if (![self indexPathIsInMenuItemSection:indexPath.section]) {
BOOL reset = self.menu == m;
if (reset) [self reloadTableView:self.tableView withMenu:nil animated:YES autoscroll:NO];
else [self reloadTableView:self.tableView withMenu:m animated:YES autoscroll:YES];
}
}
There were a couple of helpers mentioned in there:
- (BOOL)indexPathIsInMenuItemSection:(NSInteger)section
{
// returns YES if section refers to our MenuItemCells
int indexOfMenu = [self.restaurant getIndexOfMenu:self.menu];
return indexOfMenu != -1 && section == indexOfMenu+1;
}
- (void)reloadTableView:(UITableView *)tableView withMenu:(Menu *)menu animated:(BOOL)animated autoscroll:(BOOL)autoscroll
{
int oldIndex = [self.restaurant getIndexOfMenu:self.menu];
int newIndex = [self.restaurant getIndexOfMenu:menu];
[tableView beginUpdates];
if (oldIndex != -1) {
// index of [section for items] is oldIndex+1
[tableView deleteSections:[NSIndexSet indexSetWithIndex:oldIndex+1] withRowAnimation:UITableViewRowAnimationTop];
}
if (newIndex != -1) {
// index for [section for items] is newIndex+1
[tableView insertSections:[NSIndexSet indexSetWithIndex:newIndex+1] withRowAnimation:UITableViewRowAnimationTop];
[self setMenu:menu];
} else {
// no new menu
[self setMenu:nil];
}
[tableView endUpdates];
if (autoscroll) [self autoscroll];
}
- (void)autoscroll
{
if (self.menu != nil) {
int section = [self.restaurant getIndexOfMenu:self.menu];
if (section != -1) {
NSUInteger indexes[] = {section,0};
NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
}
Since my data is loaded asynchronously elsewhere, I have this controller set up to receive an NSNotification, but it should work just as well to call this on viewDidAppear:
[self reloadTableView:self.tableView withMenu:self.menu animated:YES autoscroll:YES];
I hope this helps! Let me know if I can clarify any of it.

Dynamically add rows to multiple sections in grouped table

I've dug through and found several examples of dynamically adding rows to a UITableView just not to a grouped table and I just can't figure this out. I know what I want to do - I have a grouped table with 3 sections. I want to dynamically add an 'Add new item' row/cell to sections 1 and 2 but not 0 when the edit button is selected.
First I'm confused about numberOfRowsInSection. I'm initially loading my table with this.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [[[tableData objectAtIndex:section] objectForKey:#"Rows"] count];
}
Do I need to add an if statement for when I'm editing to add a row to the count for those sections when I'm editing? such as:
if (editing) {
return [[[tableData objectAtIndex:section] objectForKey:#"Rows"] count] + 1;
}
And I realize that the above, if correct, would add a row to each section not just 1 and 2. How would I limit that?
Next is my setEditing function. This is where my real problem is. I believe I need to make an array of index paths of the last rows of sections 1 and 2 so that I can insert the new rows below them. The below is wrong, just my experimentation trying to get something inserted somewhere.
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
[self.tableView setEditing:editing animated:animated];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:1 inSection:1];
NSMutableArray* paths = [[NSMutableArray alloc] initWithObjects:indexPath, nil];
NSArray *paths = [NSArray arrayWithObject:
[NSIndexPath indexPathForRow:6 inSection:1]];
if (editing) {
[[self tableView] insertRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop]; }
else {
[[self tableView] deleteRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop]; }
}
That crappy code returns this error:
Invalid update: invalid number of rows
in section 1. The number of rows
contained in an existing section after
the update (5) must be equal to the
number of rows contained in that
section before the update (5), plus or
minus the number of rows inserted or
deleted from that section (1 inserted,
0 deleted).
So I'm a bit lost. IndexPaths and Arrays are new to me. Actually it's all new to me and I do pour over the docs and posts here but I can't say I always understand it. Any help would be appreciated. I also do realize that I still need to configure my cell and commitEditingStyle methods but I think I can figure that out if I can get a handle on understanding index paths and arrays.
thanks
-----EDIT-----
Ok, so I got this for the most part. I've added a new row to my sections when editing:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(self.editing && section != 0) {
int rows = 0;
rows = [[[tableData objectAtIndex:section] objectForKey:#"Rows"] count] + 1;
return rows;
}
else {
int rows = 0;
rows = [[[tableData objectAtIndex:section] objectForKey:#"Rows"] count];
return rows;
}
}
I've figured out how to apply a label to those new rows when in editing mode:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier= #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
//configure the cell...
if(self.editing && indexPath.section != 0 && (indexPath.row == [[[tableData objectAtIndex:indexPath.section] objectForKey:#"Rows"] count])) {
cell.textLabel.text = #"Add new Item";
return cell;
}
else
cell.textLabel.text = [[[tableData objectAtIndex:indexPath.section] objectForKey:#"Rows"] objectAtIndex:indexPath.row];
return cell;
And I'm properly setting the last row of my two sections where the row was added to style insert:
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0)
//Can't edit the items in the first section
return UITableViewCellEditingStyleNone;
else
//if it's the last row in sections 1 or 2 make it an Insert item
if(self.editing && indexPath.section != 0 && (indexPath.row == [[[tableData objectAtIndex:indexPath.section] objectForKey:#"Rows"] count])) {
return UITableViewCellEditingStyleInsert;
}
//Everything else in sections 1 or 2 should be Delete style
else
return UITableViewCellEditingStyleDelete;
}
That all works. Of course it doesn't animate and I still need help with that. I think, but I'm not certain that I need to do the insert/delete rows from the setEditing method. What I believe I need to do is make an array to the sections that I'm editing and then insert the rows there. The *paths array below is wrong because I need an array that points to the last rows in sections 1 & 2 and I don't know how to create that array. Or maybe everything I'm thinking is just wrong. Can someone help point the way? Much thanks.
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
[self.tableView setEditing:editing animated:animated];
NSArray *paths = [NSArray arrayWithObject:
[NSIndexPath indexPathForRow:?? inSection:??]];
if (editing) {
[[self tableView] insertRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop]; }
else {
[[self tableView] deleteRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop]; }
}
Do I need to add an if statement for when I'm editing to add a row to the count for those sections when I'm editing?
Yes, in your tableView:numberOfRowsInSection: method, you should return count + 1 in the editing mode. If you do not want to add a row to section 0 then adjust your if condition, i.e.
if( self.editing && section != 0)
Your second problem is related to the first one. You are inserting a new row to the section 1 but
numberOfRowsInSection still returns 5 for that section. It should return 6.
The methods insertRowsAtIndexPaths:withRowAnimation: and deleteRowsAtIndexPaths:withRowAnimation:
are just to reload the table efficiently and with animation. I suggest you to implementsetEditing:animated function as below for now. Then you should implement your tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: methods by taking editing state into account. After everything is working properly, you can use insert and delete methods to have more control over which parts of the table are reloaded.
- (void)setEditing:(BOOL)editing animated:(BOOL)animated{
[super setEditing:editing animated:animated];
[self.tableView reloadData];
}
--EDIT--
You are in the correct path. You can get the indexPaths to delete/insert with a code similar to below:
NSArray *paths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:[[[tableData objectAtIndex:1] objectForKey:#"Rows"] count] inSection:1],
[NSIndexPath indexPathForRow:[[[tableData objectAtIndex:2] objectForKey:#"Rows"] count] inSection:2],
nil
];

Resources