I have a static tableview that was designed using storyboard. Whenever I select one cell and call reloadSections:withRowAnimation: it causes the two cells above it to disappear but displays the 4 cells that it should. Anybody know why this is happening?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.section == 1) {
if (indexPath.row == 0) {
}
else if (indexPath.row == 1) { // Map Type Cell
self.isSelectingMapType = ![self isSelectingMapType];
[self.tableView beginUpdates];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
else {
// Configure the single selection
if (self.checkedIndexPath) {
UITableViewCell *uncheckCell = [tableView cellForRowAtIndexPath:self.checkedIndexPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
// Check the cell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
// Store the new indexPath
self.checkedIndexPath = indexPath;
// Save the map type indexPath
[LBSettings saveObject:indexPath forKey:kLBSettingsMapTypeIndexPath];
// Save the map type
if (indexPath.row == 2) {
// Save the map type standard
[LBSettings saveObject:[NSNumber numberWithInt:kGMSTypeNormal] forKey:kLBSettingsMapType];
}
if (indexPath.row == 3) {
// Save the map type satellite
[LBSettings saveObject:[NSNumber numberWithInt:kGMSTypeSatellite] forKey:kLBSettingsMapType];
}
if (indexPath.row == 4) {
// Save the map type hybrid
[LBSettings saveObject:[NSNumber numberWithInt:kGMSTypeHybrid] forKey:kLBSettingsMapType];
}
if (indexPath.row == 5) {
// Save the map type terrian
[LBSettings saveObject:[NSNumber numberWithInt:kGMSTypeTerrain] forKey:kLBSettingsMapType];
}
self.isSelectingMapType = ![self isSelectingMapType];
[self.tableView beginUpdates];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in each section
switch (section) {
case 0:
return 3;
break;
case 1:
if (self.isSelectingMapType == YES) {
return 6;
}
return 2;
break;
case 2:
return 2;
break;
case 3:
return 6;
break;
case 4:
return 0;
break;
default:
break;
}
return 0;
}
Try to call reloadData after the animation transaction is finished.
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[self.tableView reloadData];
}];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
[CATransaction commit];
Related
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;
}
I'm currently working on an implementation of an inline UIDate picker inside of a UITableViewCell.
I'm able to show and hide this picker cell when I select the cell directly above where that cell should be inserted, which is the behavior that I expect. However, the app crashes if I select any other cells in the table view:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3318.16.14/UITableView.m:1582
After looking at the accepted answer to this SO question, I added an exception breakpoint, and I've found out that the app is crashing at the call to [tableView endUpdates]; in didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
// Check to see if the "Only Alert From" row was selected. The cell with the picker should be below this one.
if (indexPath.section == TimeOfDaySection && indexPath.row == HourTimeZoneRow && self.timePickerIsShowing == NO){
[tableView beginUpdates];
[self showTimePicker];
[tableView endUpdates];
} else{
[tableView beginUpdates];
[self hideTimePicker];
[tableView endUpdates];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
That said, I'm not sure how to proceed. If I comment out the call to [tableView endUpdates];, the app won't crash when other cells are selected, BUT the cell with the picker view won't hide. Does anyone have any suggestions? Thank you!
EDIT: Below is my code for showTimePicker and hideTimePicker:
- (void)showTimePicker
{
self.timePickerIsShowing = YES;
self.timePicker.hidden = NO;
//Create the index path where we insert the cell with the picker
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:HourTimeZoneRow + 1 inSection:TimeOfDaySection];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
self.timePicker.alpha = 0.0f;
[UIView animateWithDuration:0.25 animations:^{
self.timePicker.alpha = 1.0f;
//This is the row where the picker cell should be inserted
[self.tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:HourTimeZoneRow + 1 inSection:TimeOfDaySection]] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
}];
}
- (void)hideTimePicker {
self.timePickerIsShowing = NO;
self.timePicker.hidden = YES;
//Create the index path where we delete the cell with the picker
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:HourTimeZoneRow + 1 inSection:TimeOfDaySection];
[self.tableView beginUpdates];
//Delete the picker row
[self.tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
[UIView animateWithDuration:0.25
animations:^{
self.timePicker.alpha = 0.0f;
}
completion:^(BOOL finished){
self.timePicker.hidden = YES;
}];
}
EDIT 2: After reading this SO thread, I believe the problem may be with my numberOfRowsInSection method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
switch (section) {
case NotificationsSection:
return TotalPreferencesRows;
break;
case RedZoneSection:
return TotalRedZoneRows;
break;
case TimeOfDaySection:
if (self.timePickerIsShowing) {
return TotalTimeOfDayRows + 1;
}
// else if (self.timePickerIsShowing == NO){
// return TotalTimeOfDayRows;
// }
else{
return TotalTimeOfDayRows;
}
return TotalTimeOfDayRows;
break;
default:
return 0;
break;
}
}
Not sure if this is the source of the crash, but it's not recommended to call beginUpdates multiple times which you're doing in
[tableView beginUpdates];
[self showTimePicker];
[tableView endUpdates];
because showTimePicker calls [self.tableView beginUpdates];
The problem is in your didSelectRowAtIndexPath, you call the hide method even though it might not be showing. Make the else clause into an else if, like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
// Check to see if the "Only Alert From" row was selected. The cell with the picker should be below this one.
if (indexPath.section == TimeOfDaySection && indexPath.row == HourTimeZoneRow && self.timePickerIsShowing == NO){
[tableView beginUpdates];
[self showTimePicker];
[tableView endUpdates];
} else if (indexPath.section == TimeOfDaySection && indexPath.row == HourTimeZoneRow && self.timePickerIsShowing == YES){
[tableView beginUpdates];
[self hideTimePicker];
[tableView endUpdates];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
So I have a deadline coming up for this project and I've been working hard on it and so I'm pretty tired right now. I don't know if I'm just being stupid and blind to what I'm doing wrong but I have a problem that I need help with.
So, I'm trying to implement a sort of "expanding tableview setup" (like in IOS 7 calendar where you tap on the start and end dates and a cell pops in with a date picker). I have two different cells that can be displayed under a tapped cell. It works perfectly except for the actual cell, when I insert a row the wrong cell is displayed. I'll give some context... I have two cells in section 0, when you tap on the first cell it inserts a cell below it that should be a picker cell. Instead it inserts the same call as in row 1 of the unmodified tableview.
Here is the relevant code:
#interface AddMealTableViewController ()
#property (nonatomic) BOOL datePickerEnabled;
#property (nonatomic) BOOL pickerEnabled;
#property (nonatomic, strong) NSIndexPath *datePickerIndexPath;
#property (nonatomic, strong) NSIndexPath *pickerIndexPath;
#end
#implementation AddMealTableViewController
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
switch (section) {
case 0:
return 2 + (self.datePickerEnabled) + (self.pickerEnabled);
break;
default:
return self.mealComponents.count;
break;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section) {
case 0:
{
if (indexPath.row == 1) {
TimeCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TimeCell"];
cell.timeLabel.text = #"Now";
cell.mealTypeLabel.text = self.selectedMealType;
return cell;
}
else if (indexPath == self.datePickerIndexPath) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"DateCell"];
return cell;
}
else if (indexPath == self.pickerIndexPath) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"PickerCell"];
return cell;
}
else {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MealTypeCell"];
cell.textLabel.text = #"Meal Type";
cell.detailTextLabel.text = self.selectedMealType;
return cell;
}
break;
}
default:
{
ComponentCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ComponentCell"];
if (!cell) {
cell = [ComponentCell new];
}
MealComponent *mc = self.mealComponents[indexPath.row];
[cell setUpWithComponent:mc];
return cell;
break;
}
}
return nil;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
if (indexPath == self.datePickerIndexPath || indexPath == self.pickerIndexPath) {
return 200;
}
return self.tableView.rowHeight;
}
return 70;
}
- (void)toggleDatePickerForSelectedIndexPath:(NSIndexPath *)indexPath
{
[self.tableView beginUpdates];
NSArray *indexPaths = #[[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:0]];
if (self.datePickerEnabled)
{
self.datePickerEnabled = NO;
self.datePickerIndexPath = nil;
[self.tableView deleteRowsAtIndexPaths:indexPaths
withRowAnimation:UITableViewRowAnimationFade];
}
else
{
self.datePickerIndexPath = indexPaths.lastObject;
self.datePickerEnabled = YES;
[self.tableView insertRowsAtIndexPaths:indexPaths
withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
}
- (void)togglePickerForSelectedIndexPath:(NSIndexPath *)indexPath
{
[self.tableView beginUpdates];
NSArray *indexPaths = #[[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:0]];
if (self.pickerEnabled)
{
self.pickerEnabled = NO;
self.pickerIndexPath = nil;
[self.tableView deleteRowsAtIndexPaths:indexPaths
withRowAnimation:UITableViewRowAnimationFade];
}
else
{
self.pickerIndexPath = indexPaths.lastObject;
self.pickerEnabled = YES;
[self.tableView insertRowsAtIndexPaths:indexPaths
withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if ([cell.reuseIdentifier isEqual: #"TimeCell"]) {
[self toggleDatePickerForSelectedIndexPath:indexPath];
}
else if ([cell.reuseIdentifier isEqual: #"MealTypeCell"]) {
[self togglePickerForSelectedIndexPath:indexPath];
}
}
}
Any ideas? I really can't seem to put my finger on it.
why don't you change the height of date picker cell from 0 to 200 instead insert (or delete) the date picker cell. I think this is more simple.(This is what I'm used to implement that)
when [tableView didSelectRowAtIndexPath:] is called, you just call [tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic] to update cell.
You can managed the height of cell in [tableView heightForRowAtIndexPath:] you already know.
[edited]
I examine your code again.
change else if (indexPath == self.datePickefIndexPath) to else if([indexPath isEqual:self.datePickerIndexPath]) in [tableView:cellForRowAtIndexPath:]
There're many ways to handle this kind of things. You really don't need to insert or delete cell, just modify the cell height from 0 to the proper height. To do this, you can set a flag to check whether the cell's expanded or not.
I have searched and search and i just canĀ“t seem to find an answer to my problem. I have a dynamic tableview with 3 rows (each row is a section) and a edit button at the top right of the tableview. each time the user taps edit it has to be possible to add or delete a row. Everything works except the part when the + button is taped to ad a new row. This is my code:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int count = [myarray count];
if (myarray != nil) count ++;
return count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
id cell;
switch(indexPath.section) {
case 0:
if(indexPath.row==0) {
static NSString *cellType1 = #"cellType1";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellType1];
return cell;
}
break;
case 1:
if(indexPath.row==0) {
static NSString *cellType2= #"cellType2";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellType2];
return cell;
}
break;
case 2:
if(indexPath.row==0) {
static NSString *cellType3= #"cellType3";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellType3];
return cell;
}
break;
default:
break;
}
return cell;
}
- (IBAction) EditTable:(id)sender
{
if(self.editing)
{
[super setEditing:NO animated:NO];
[Table setEditing:NO animated:NO];
[Table reloadData];
[self.navigationItem.rightBarButtonItem setTitle:#"Edit"];
[self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStylePlain];
}
else
{
[super setEditing:YES animated:YES];
[Table setEditing:YES animated:YES];
[Table reloadData];
[self.navigationItem.rightBarButtonItem setTitle:#"Ok"];
[self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStyleDone];
}
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.editing == NO || !indexPath) return UITableViewCellEditingStyleNone;
if (self.editing && indexPath.row == ([myarray count]))
{
return UITableViewCellEditingStyleInsert;
} else
{
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleNone;
}
- (void)tableView:(UITableView *)TableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[myarray removeObjectAtIndex:indexPath.row];
[Table reloadData];
} else if (editingStyle == UITableViewCellEditingStyleInsert)
{
switch(indexPath.section) {
case 0:
if(indexPath.row==0) {
static NSString *cellType1 = #"cellType1";
UITableViewCell *cell = [TableView dequeueReusableCellWithIdentifier:cellType1];
[arry insertObject:cell atIndex:[myarray count]];
[Table reloadData];
}
break;
case 1:
if(indexPath.row==0) {
static NSString *cellType2= #"cellType2";
UITableViewCell *cell = [TableView dequeueReusableCellWithIdentifier:cellType2];
[arry insertObject:cell atIndex:[myarray count]];
}
break;
case 2:
if(indexPath.row==0) {
static NSString *cellType3= #"cellType3";
UITableViewCell *cell = [TableView dequeueReusableCellWithIdentifier:cellType3];
[arry insertObject:cell atIndex:[myarray count]];
}
break;
default:
break;
}
}
As i said before, everything works until i press the + button (that appears on the left side when the edit button is pressed) to add a new row. Then it shows an error: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath.
What am i doing wrong? any help would be most appreciated.
First off, you never actually +alloc or -init a cell, so -cellForRowAtIndexPath: is most likely returning nil (-dequeueReusableCellWithIdentifier: doesn't cut it).
Second off, comparing indexPath.row to [myarray count] will never be true, because arrays and tables may be zero-based, but their counts aren't.
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.