Empty sortDescriptors and table view sections - iphone

There is something within table sections and sortDescriptors that is driving me crazy. Any help will be much appreciated ;)
What I'm looking for is just displaying an "add" row at the bottom of each self.fetchedResultsController.fetchedObjects section. I'm using a sectionNameKeyPath to show sections in my table view. Up to here everything works perfectly. I've implemented this technique in other parts of my app and works pretty well.
However, the problem comes up when there is no data returned by the FRC and core data doesn't return any entity. If I try to add my first managed object, then app breaks with the following error:
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 (1), plus or minus the number of sections
inserted or deleted (1 inserted, 0 deleted). with userInfo (null)
This is my code so far:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if ([self.fetchedResultsController.fetchedObjects count] == 0) {
return 1;
}
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if ([self.fetchedResultsController.fetchedObjects count] == 0) {
return 1;
}
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects] + 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.fetchedResultsController.fetchedObjects count] == 0 ) {
static NSString *identificadorCelda2 = #"CeldaNuevoGasto";
UITableViewCell *celda = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identificadorCelda2];
if (celda == nil) {
celda = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identificadorCelda2];
}
return celda;
}
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:indexPath.section];
NSInteger nsection = [sectionInfo numberOfObjects];
if (indexPath.row == nsection) {
static NSString *identificadorCelda2 = #"CeldaNuevoGasto";
UITableViewCell *celda = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identificadorCelda2];
if (celda == nil) {
celda = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identificadorCelda2];
}
return celda;
}
static NSString *CellIdentifier = #"CeldaGasto";
CeldaGastos *cell = (CeldaGastos *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = (CeldaGastos *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
} .....
For sure I'm doing something wrong, but I can not figure it iut. Do I need to modify NSFetchedResultsController delegate methods as well to respond to additions? Or is it something easiser. Thanks ;)
Update
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
NSInteger nsection = [self.fetchedResultsController.sections count];
switch(type) {
case NSFetchedResultsChangeInsert:
if (nsection == 1 && [sectionInfo numberOfObjects] == 1) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
if (!nsection)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}

If I understand your situation correctly, then you indeed will have to change the NSFetchedResultsController delegate methods as well.
Are you inserting new sections into table view using the -insertSections:withRowAnimation: method of your table view from some of the NSFetchedResultsController's delegate callbacks?
I do not know exactly what your NSFetchedResultsControllerDelegate's implementation is, but for example consider this situation step by step:
Initially you have no entities fetched, and your -numberOfSectionsInTableView: method returns 1 for your 'add' row.
After you've inserted an entity and the NSFetchedResultsController's delegate receives its -controller:didChangeSection:atIndex:forChangeType: callback, where you insert a new section into table view using -insertSections:withRowAnimation:
The table view asks its dataSource by calling the -numberOfSectionsInTableView: method expecting it to rerurn a value increased by 1 (since you are inserting 1 new section). But the method does still return 1 because the 'add' row is not taken into account anymore.
That will result exactly in the error which you are having.
I guess calling -deleteSections:withRowAnimation: to remove the 'add' row when it is not needed anymore will solve your problem.

Related

Adding dynamic sub-rows by selecting tableview row in tableview iPhone errors?

I want to add row dynamically. I have tableview list of building names. If some one choose building(didSelectRowAtIndexPath) then respective floors of building should get added dynamically as subrow. Its like maximizing and minimizing the subrow on respective building list selection. How do I do this. Thanks in advance...
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// There is only one section.
if (tableView == indoortable || tableView == indoortable_iPad)
{
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of time zone names.
if (tableView == indoortable || tableView == indoortable_iPad)
{
return [indoorZones count];
}
}
cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == indoortable || tableView == indoortable_iPad)
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleGray; //cell bg
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Set up the cell.
//cell.textLabel.text = [copyListOfItems objectAtIndex:indexPath.row];
cell.textLabel.text =[indoorZones objectAtIndex: indexPath.row];
//[cell setIndentationLevel:[[self.indoorZones objectAtIndex:indexPath.row] intValue]];
return cell;
}
}
didSlectRowAtIndexPath method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
zonesFloor = [[NSMutableArray alloc]init];
zonesFloorA = [[NSArray alloc] initWithObjects:#"Gr fl",#"1st fl",#"2nd fl",nil];
[zonesFloor addObject:zonesFloorA];
if (tableView == indoortable )
{
NSUInteger i=indexPath.row+1;
for (NSArray *count in self.indoorZones) //app is crashing here giving error.......Collection <__NSArrayM: 0x4b1d550> was mutated while being enumerated.
{
[zonesFloor addObject:[NSIndexPath indexPathForRow:i inSection:0]];
[self.indoorZones insertObject:zonesFloor atIndex:i++];
}
[[self indoortable] beginUpdates];
[[self indoortable] insertRowsAtIndexPaths:(NSArray *)zonesFloor withRowAnimation:UITableViewRowAnimationNone];
[[self indoortable] endUpdates];
}
if (tableView == indoortable_iPad )
{
//some logic
}
[tableView deselectRowAtIndexPath:selectedIndexPath animated:NO];
}
It Gives following error [__NSArrayI compare:]: Or [NSIndexPath _fastCStringContents:]: unrecognized selector sent to instance. I tried many ways but may be I am lacking somewhere. Please suggest. thanks in advance.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//NSIndexPath *selectedIndexPath = [self.indoortable indexPathForSelectedRow];
zonesFloorA = [[NSArray alloc] initWithObjects:#"Gr fl",#"1st fl",#"2nd fl",nil];
if (tableView == indoortable )
{
for (NSString *str in zonesFloorA) {
[indoorZones addObject:str];
}
//[[self indoortable] beginUpdates];
//[[self indoortable] insertRowsAtIndexPaths:(NSArray *)zonesFloor withRowAnimation:UITableViewRowAnimationNone];
//[[self indoortable] endUpdates];
}
if (tableView == indoortable_iPad )
{
//some logic
}
[tableView reloadData];
}
may this meet your requirement
Okay, so not to sound mean, but there are almost too many issues here to count.
Let's start with a basic explanation of how tableView's work so that you can start to fix this:
First, the tableView asks how many sections are in the table by calling:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
}
In your code, you simply tell it that it has one section. However, in your later code, when you try to add rows to your table, you tell it that you want to add your rows to a second section (with an index of 1). Therefore, you either need to add these rows to section 0 instead, or update the above method to tell it that, sometimes, there are two sections.
Second, the tableView asks how many rows are in each section of the table by calling:
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
}
In your code, you are simply returning the number of zones. However, like above, you need to include the rows that you have added to your table. If you are adding them to a different section, then you need to return different values, depending on how many rows are in the section with the index asked for in the section variable. If they are all in the same section, then you need to add them up and return the correct value.
Third, the tableView asks for an actual cell for the row by calling:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
}
In your code, you are only returning a cell which has data populated by the indoorZones array, but you also need to supply cells which are configured properly for the specific zone/floor. Again, you either need to determine this by section number or row number as appropriate.
Finally, when you click on a row, the tableview tells you by calling the following method:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
In this method, you need to update your data source that is used by the previous functions so that when they get called again, they will provide the correct data. Your data source must mirror the way that you want your table view to look. In your case, you have an array called indoorZones. This is good if you just want to display a list of zones, which is what you start off doing. However, when you want to add more rows, you need to add more rows to your data source first, so that when the tableView starts this process over, it is already there.
If you want everything to stay in one section, then I would come up with a data source that can include both types of rows, and be able to distinguish between them so that cellForRowAtIndexPath can create the proper type of cell and return it.
If you want two sections, then I would add a second array for the second section (since it is not the same type of data) and return the appropriate values in each of these methods, based on which array you need to use for that section.
I hope this helps!

NSFetchedResultsController - Delegate methods crashing under iPhone OS 3.0, but NOT UNDER 3.1

Hey guys, so I've got my NSFetchedResultsController working fine under the 3.1 SDK, however I start getting some weird errors, specifically in the delegate methods when I try it under 3.0. I've determined that this is related to the NSFetchedResultsControllerDelegate methods. This is what I have set up.
The inEditingMode stuff has to do with the way I've implemented adding another static section to the table.
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type{
NSIndexSet *sectionSet = [NSIndexSet indexSetWithIndex:sectionIndex];
if(self.inEditingMode){
sectionSet = [NSIndexSet indexSetWithIndex:sectionIndex + 1];
}
switch (type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:sectionSet withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:sectionSet withRowAnimation:UITableViewRowAnimationFade];
break;
default:
[self.tableView reloadData];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath{
NSIndexPath *relativeIndexPath = indexPath;
NSIndexPath *relativeNewIndexPath = newIndexPath;
if(self.inEditingMode){
relativeIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section + 1];
relativeNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:newIndexPath.section + 1];
}
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:relativeNewIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:relativeIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
default:
[self.tableView reloadData];
break;
}
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
[self.tableView endUpdates];
}
When I add an entity to the managed object context, I get the following error:
Serious application error. Exception was caught during Core Data change processing: *** -[NSCFArray objectAtIndex:]: index (1) beyond bounds (1) with userInfo (null)
I put a breakpoint on objc_exception_throw, and the crash seems to be occuring inside of controllerDidChangeContent.
If I comment out all of the self.tableView methods, and put a single [self.tableView reloadData] inside of controllerDidChangeContent, everything works as expected.
Anybody have any idea as to why this is happening?
In the documentation for NSFetchedResultsController, there is specific mention of a bug in the 3.0 implementation that results in a discrepancy between the number of sections reported by the controller and the number of sections expected by the UITableView. This is the workaround they provide:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSUInteger count = [[<#Fetched results controller#> sections] count];
if (count == 0) {
count = 1;
}
return count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSArray *sections = [<#Fetched results controller#> sections];
NSUInteger count = 0;
if ([sections count]) {
id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section];
count = [sectionInfo numberOfObjects];
}
return count;
}
Note that this workaround is not required for OS 3.1, so this may explain why you are not seeing errors. The workaround is only necessary in 3.0 when sectionNameKeyPath is set to nil. If you are setting a value for sectionNameKeyPath, then this is likely not the issue.

Core Data: UITableView with multiple NSFetchedResultControllers

What I want to do is pretty simple. In my UITableViewController, I want to load data from multiple NSFetchedResultControllers (I have multiple entities in my data model) and put data from each one into a different section in the table view. So for example, all the fetched items from the first NSFetchedResultController would go in section 0 in the UITableView, the fetched items from the other one goes into section 1, etc.
The Core Data template project doesn't demonstrate how to do this. Everything (mainly the index paths) is coded without taking sections into account (there are no sections in the default template) and everything is taken from a single NSFetchedResultController. Are there any example projects or documentation that demonstrates doing this?
Thanks
Assume for a moment the following in your header (code below will be slightly sloppy, my apologies):
NSFetchedResultsController *fetchedResultsController1; // first section data
NSFetchedResultsController *fetchedResultsController2; // second section data
Let the table know you want to have 2 sections:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2; // you wanted 2 sections
}
Give it the section titles:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [NSArray arrayWithObjects:#"First section title", #"Second section title", nil];
}
Let the table know how many rows there are per sections:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return [[fetchedResultsController1 fetchedObjects] count];
} else if (section == 1) {
return [[fetchedResultsController2 fetchedObjects] count];
}
return 0;
}
Build the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
... // table cell dequeue or creation, boilerplate stuff
// customize the cell
if (indexPath.section == 0) {
// get the managed object from fetchedResultsController1
// customize the cell based on the data
} else if (indexPath.section == 1) {
// get the managed object from fetchedResultsController2
// customize the cell based on the data
}
return cell;
}
Multiple fetch controllers (and possibly multiple entities) is the wrong approach. The correct solution is to use the sectionNameKeyPath param to the NSFetchedResultController to group the results into multiple sections. If you think about your entities differently perhaps they are actually the same entity and instead you can use a property itemType which you can then section on (and you must also sort on it too). E.g. say I had entities Hops and Grains then I could change those to Ingredient and have a int_16 property ingredientType which I then have an enum in code to store the values hopType = 0, grainType = 1. After all the ingredient is just a name and a weight, which both of these share.
If however your entities really have a distinct set of properties, then the correct solution is to create a parent abstract entity that has a property that you can use to section, e.g. sortOrder, sectionID or type. When you then create a fetch controller and fetch the abstract parent entity, you actually get results containing all of the sub-entities. E.g in the Notes app they have an abstract parent entity NoteContainer that has child entities Account and Folder. This enables a single fetch controller to display the account in the first cell in the section, and then have all the folders in the following cells. E.g. All iCloud Notes (is actually the account), then Notes (is the default folder), followed by all the custom folders, then the trash folder. They use a sortOrder property and the default folder is 1, the custom folders are all 2, and the trash is 3. Then by adding this as a sort descriptor they can have the cells display in the order they want. It's a bit different from your requirement because they have the 2 entities mixed into different sections, but you can still make use of it just with different sort properties.
The moral of the story is don't fight the framework, embrace it :-)
Extending Giao's solution with two NSFetchedResultsControllers - we have to remember our NSFetchedResultsController do not know about our two sections and returned NSIndexPathes will be always for first section.
So when we are getting an object in cell configuration:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (!cell) {
[tableView registerNib:[UINib nibWithNibName:#"cell" bundle:nil] forCellReuseIdentifier:#"cell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
-(void)configureCell:(UITableViewCell*)cell atIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
NSManagedObject *object = [self.fetchedResults1 objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
//use object to configure cell
} else {
NSManagedObject *object = [self.fetchedResults2 objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
//use object to configure cell
}
}
Updating cells while NSFetchedResultsController noticed some changes:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
NSIndexPath *customIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:(controller == self.fetchedResultsController1)?0:1];
NSIndexPath *customNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:(controller == self.fetchedResultsController2)?0:1];
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:customNewIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:customIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:customIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:customNewIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}

How to use tableView:editingStyleForRowAtIndexPath?

I have a very simple application with a UITableViewController. Upon EDITING, I am trying to slide a row into position 0 of the first section. The new row should have an INSERT editing style while the existing row should have a DELETE style.
I've overridden the following 4 methods:
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.editing && section == 0) {
return2;
}
return 1;
}
- (UITableViewCellEditingStyle)tableView:(UITableView*)tableView
editingStyleForRowAtIndexPath:(NSIndexPath*)indexPath {
int section = indexPath.section;
int row = indexPath.row;
if (self.editing && section == 0 && row == 0) {
return UITableViewCellEditingStyleInsert;
}
return UITableViewCellEditingStyleDelete;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
NSIndexPath *ip = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView beginUpdates];
if (editing) {
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:ip]
withRowAnimation:UITableViewRowAnimationLeft];
} else {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:ip]
withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ClientsControllerCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc ] initWithStyle:UITableViewCellStyleValue1reuseIdentifier:CellIdentifier] autorelease];
}
int section = indexPath.section;
int row = indexPath.row;
if (self.editing && section == 0 && row == 0) {
cell.textLabel.text = #"Add Me";
cell.detailTextLabel.text = #"Detail text";
} else {
cell.textLabel.text = #"Test me";
cell.detailTextLabel.text = #"Detail text";
}
return cell;
}
But as soon as I go into EDIT mode, "both" cells end up with an editing style of UITableViewCellEditingStyleInsert.
If I change my logic and append the new cell to the END - then it correctly draws the cells with a DELETE style and the new cell get's an INSERT.
Either way, tableView:editingStyleForRowAtIndexPath gets invoked 4 times. In fact, if I insert the new cell into section:0 row:0, this method gets called with section:row 0:0, 0:1, 0:0, 0:0. Whereas if I append the new cell into section: row:1, this method gets called with section:row 0:0, 0:1, 0:0, 0:1.
What am I missing? I should be able to insert a row and catch it right? For some reason, I can't see section=0 row=1 come through a second time.
-Luther
There's another question on StackOverflow that appears to ask essentially the same thing: SO 1508066.
The answer there claims it's nonstandard to put the Insert row at the top; it should go at the bottom instead. I'm not sure I agree with that contention, but it's certainly the path of least resistance.

Inserting and deleting UITableViewCell at the same time not working

I'm having quite a bit of pain inserting and deleting UITableViewCells from the same UITableView!
I don't normally post code, but I thought this was the best way of showing where I'm having the problem:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 5;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (iSelectedSection == section) return 5;
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//NSLog(#"drawing row:%d section:%d", [indexPath row], [indexPath section]);
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
if (iSelectedSection == [indexPath section]) {
cell.textColor = [UIColor redColor];
} else {
cell.textColor = [UIColor blackColor];
}
cell.text = [NSString stringWithFormat:#"Section: %d Row: %d", [indexPath section], [indexPath row]];
// Set up the cell
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic -- create and push a new view controller
if ([indexPath row] == 0) {
NSMutableArray *rowsToRemove = [NSMutableArray array];
NSMutableArray *rowsToAdd = [NSMutableArray array];
for(int i=0; i<5; i++) {
//NSLog(#"Adding row:%d section:%d ", i, [indexPath section]);
//NSLog(#"Removing row:%d section:%d ", i, iSelectedSection);
[rowsToAdd addObject:[NSIndexPath indexPathForRow:i inSection:[indexPath section]]];
[rowsToRemove addObject:[NSIndexPath indexPathForRow:i inSection:iSelectedSection]];
}
iSelectedSection = [indexPath section];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:rowsToRemove withRowAnimation:YES];
[tableView insertRowsAtIndexPaths:rowsToAdd withRowAnimation:YES];
[tableView endUpdates];
}
}
This code creates 5 sections, the 1st (indexed from 0) with 5 rows. When you select a section - it removes the rows from the section you had previously selected and adds rows to the section you just selected.
Pictorally, when I load up the app, I have something like this:
http://www.freeimagehosting.net/uploads/1b9f2d57e7.png http://www.freeimagehosting.net/uploads/1b9f2d57e7.png
Image here: http://www.freeimagehosting.net/uploads/1b9f2d57e7.png
After selecting a table row 0 of section 2, I then delete the rows of section 1 (which is selected by default) and add the rows of section 2. But I get this:
http://www.freeimagehosting.net/uploads/6d5d904e84.png http://www.freeimagehosting.net/uploads/6d5d904e84.png
Image here: http://www.freeimagehosting.net/uploads/6d5d904e84.png
...which isn't what I expect to happen! It seems like the first row of section 2 somehow remains - even though it definitly gets deleted.
If I just do a [tableView reloadData], everything appears as normal... but I obviously forefit the nice animations.
I'd really appreciate it if someone could shine some light here! It's driving me a little crazy!
Thanks again,
Nick.
Struggled to get this to work. Here's my code to add a row to my tableView:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[tableView beginUpdates];
[dataSource insertObject:[artistField text] atIndex:0];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[tableView endUpdates];
I seem to remember that numberOfRowsInSection: will get called when you call deleteRows or insertRow, you need to be really careful that the reality numberOfRowsInSection cliams matches your changes. In this case you may want to try moving the iSelectedSection = [indexPath section]; line to after the endUpdates.
I don't remember where I read this but I believe you shouldn't perform table row updates (insertions and deletions) from inside one of the table view delegate functions. I think a better alternative would be to do a performSelectorOnMainThread passing along the necessary information needed to perform the updates as an object. Something like:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// ....
[self performSelectorOnMainThread: #selector(insertRows:)
withObject: someObjectOrNil]; // double check args
}
- (void) insertRows: (NSObject*)someObjectOrNil {
[tableView beginUpdates];
// update logic
[tableView endUpdates];
// don't call reloadData here, but ensure that data returned from the
// table view delegate functions are in sync
}
In the code you posted, your loop index runs from 0 to 4, which suggests that it would delete all of the rows in section 1, and then add five new rows to section 2. Since each section already has a row 0, this would add a second instance of section 2, row 0 to the table.
I would suggest having your loop run from 1 to 4:
for (int i=1; i<5; i++)
{
// ...
}
FYI: This bug seems to have been fixed completely with the 2.2 iPhone update.
Thanks Apple!
Nick.