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.
Related
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;
}
I am trying to show a table view with 2 sections. The first section will always have 1 row and the second section will have as many rows as data points. I am using Core Data and the following tableView:numberOfRowsInSection: method...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return 1;
} else {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
}
However, I am getting the following error:
Terminating app due to uncaught exception 'NSRangeException', reason:
'* -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
Any help will be appreciated. Thanks.
NEW -------------------------------------------------------------------------------
This is the current implementation of the relevant methods:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return 1;
} else {
NSUInteger frcSection = section - 1;
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:frcSection];
return [sectionInfo numberOfObjects];
}
}
- (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];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
cell.textLabel.text = entityOne.name; //entityOne object passed from previous VC
} else {
entityTwo = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = entityTwo.name;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
NSUInteger frcSectionIndex = 0;
frcSectionIndex = sectionIndex + 1;
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:frcSectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:frcSectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
The reason is that the fetched results controller (FRC) has only one section (section #0), which you want to be displayed in the second section (section #1) of the table view.
This is possible, but you have to map between FRC section numbers and table view section numbers, e.g.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return 1;
} else {
NSUInteger frcSection = section - 1;
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:frcSection];
return [sectionInfo numberOfObjects];
}
}
The same mapping is necessary in cellForRowAtIndexPath.
In the FRC delegate methods didChangeObject, didChangeSection you have to add 1 to the section number before calling the table view methods (e.g. insertRowsAtIndexPaths).
ADDED: configureCell should look like this:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
cell.textLabel.text = entityOne.name; //entityOne object passed from previous VC
} else {
NSIndexPath *frcIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:(indexPath.section - 1)];
entityTwo = [self.fetchedResultsController objectAtIndexPath:frcIndexPath];
cell.textLabel.text = entityTwo.name;
}
}
and didChangeObject like this:
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
NSIndexPath *tvIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:(indexPath.section + 1)];
NSIndexPath *tvNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:(newIndexPath.section + 1)];
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:#[tvNewIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[tvIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:tvIndexPath] atIndexPath:tvIndexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[tvIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:#[tvNewIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
You probably get the idea:
When going from FRC index path to table view index path, add one to the section.
When going from table view index path to FRC index path, subtract one from the section.
I have a tableView with some sections, which all have a footer, and then I have a tableViewFooter on the Tableview itself.
If I scroll down to the bottom of my tableview and delete the last item(therefore deleting the section altogether) in any sections above the last section (second last and up) it gives me this error
2014-02-21 13:19:55.066 xxxx[5436:60b] *** Assertion failure in -[UIViewAnimation initWithView:indexPath:endRect:endAlpha:startFraction:endFraction:curve:animateFromCurrentPosition:shouldDeleteAfterAnimation:editing:], /SourceCache/UIKit/UIKit-2903.23/UITableViewSupport.m:2661
Uncaught exception: Cell animation stop fraction must be greater than start fraction
at endUpdates
this is my code
[self.tableView beginUpdates];
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
if(indexPath != nil){
TableSection * sec = [self.sections objectAtIndex:indexPath.section];
NSMutableDictionary *dic =[sec.items objectAtIndex:indexPath.row];
Product* product = [dic valueForKey:PRODUCT];
//removing the item in the section
[sec.items removeObject:dic];
//deleting item from products
NSMutableArray *temp = [NSMutableArray array];
for (Product *p in self.dataCon.listPersister.products) {
if ([p.product.objId isEqualToString: product.product.objId]) {
[temp addObject:p];
}
}
for (Product *p in temp) {
[self.dataCon.listPersister.products removeObject:p];
}
//if it was the last object in section, delete the section else just delete the single row
if(sec.items.count == 0)
{
[self.sections removeObject:sec];
[self.footers removeObjectAtIndex:indexPath.section];
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
} else
{
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
USFooterView *footer = [self.footers objectAtIndex:indexPath.section];
footer.totalLabel.text = [self.dataCon.listPersister getTotalForShopId:sec.title];
self.footerView.totalLabel.text = [self.dataCon.listPersister getTotalForAllProducts];
}
}
[self.tableView endUpdates];
I had the same code earlier, just without my tableView and table sections having footers, where it worked, so I think they might be the problem, but I'm not entirely sure that's the reason it's acting up.
I have seen this post
UITableView tableFooterView may cause crash?
And the post that it links to, but that didn't help me.
Any help is appreciated :)
In the else statement you delete row from table view:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
But not from data source. Delete row from array which you use as data source and it should works.
I found a "fix", but I'm avoiding the use of sectionFooter, because that seems to be bugged.
I created an extra cell at the end of each section, with the same setup I had for my footer View before, and made that last cell not editable with
-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
TableSection * sec = [self.sections objectAtIndex:indexPath.section];
if (sec.items.count != indexPath.row) {
return YES;
} else
return NO;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [sec.items count] +1 ;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"normalcell";
static NSString *CellIdentifier1 = #"footercell";
TableSection * sec = [self.sections objectAtIndex:indexPath.section];
if (indexPath.row != sec.items.count) {
//use normal type of cell
return cell;
} else{
//use footer type of cell
return cell;
}
}
So the last cell Imitates a "footer", but it's not stuck to the bottom of the frame, but I'll have to live with that. It's better than crashes.
Try using UITableViewRowAnimationLeft or UITableViewRowAnimationRight as the delete row animation(deleteRowsAtIndexPaths:withRowAnimation:).
It crashed for me when using UITableViewRowAnimationAutomatic, but not with the other two. I have not tried all of them but it seems to be a bug with the animation code for some of the options.
I am just stuck here, I cannot figure it out what I am doing wrong here. I am pretty new to objective c. In my application I am using a UITableView and NSMutableArray to parse data from RSS feed. I want to refresh the whole table with new data when user click on the segment control in the tableview. I have added the segment control in table header section and it works fine with changing the value when segment index changed.
The problem is now when I click on the segment control it adds the new rows in the tableview instead of refreshing the whole table. Here is the code:
- (void)addEarthquakesToList:(NSArray *)earthquakes
{
//NSInteger startingRow = [self.earthquakeList count];
NSInteger earthquakeCount = [earthquakes count];
NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:earthquakeCount];
NSLog(#"%d",earthquakeCount);
for (NSInteger row = 0; row < (earthquakeCount); row++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
[indexPaths addObject:indexPath];
}
[self.earthquakeList addObjectsFromArray:earthquakes];
[self.tableView insertRowsAtIndexPaths:indexPaths
withRowAnimation:UITableViewRowAnimationAutomatic];
}
I have tried many other things like [self.tableView reloadData];
I have also tried the following to remove all objects from the array and remove rows from table but it does not display correct number of rows:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSMutableArray *indexPathsToDelete = [[NSMutableArray alloc] init];
for(unsigned int i = 0; i < [self.earthquakeList count]; i++)
{
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
//[self.tableView beginUpdates];
[self.earthquakeList removeAllObjects];
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete
withRowAnimation:UITableViewRowAnimationFade];
//[self.tableView endUpdates];
//[self.tableView reloadData];
}
And these are my tableView DataSource methods:
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
// The number of rows is equal to the number of earthquakes in the array.
return [self.earthquakeList count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kEarthquakeCellID = #"EarthquakeCellID";
citsTableViewCell *cell = (citsTableViewCell *)[tableView dequeueReusableCellWithIdentifier:kEarthquakeCellID];
//Get the specific earthquake for this row.
citsFuelFinder *earthquake = (self.earthquakeList)[indexPath.row];
[cell configureWithEarthquake:earthquake];
return cell;
}
In fact when try to include the following in the segment control:
- (void)segmentedControlHasChangedValue
{
int product;
product = fuelType.selectedSegmentIndex;
switch (product)
{
case 0:
productName=1;
[locationManager startUpdatingLocation];
break;
case 1:
productName=2;
//[self.earthquakeList removeLastObject];
if (![self.earthquakeList count])
{
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:0]
withRowAnimation:UITableViewRowAnimationRight];
}
else
{
NSMutableArray *indexPathsToDelete = [[NSMutableArray alloc] init];
for(unsigned int i = 0; i < [self.earthquakeList count]; i++)
{
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i
inSection:0]];
}
//[self.tableView beginUpdates];
//[self.earthquakeList removeAllObjects];
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete
withRowAnimation:UITableViewRowAnimationFade];
//[self.tableView endUpdates];
[self.tableView reloadData];
//[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
[locationManager startUpdatingLocation];
break;
case 2:
productName=4;
[locationManager startUpdatingLocation];
break;
case 3:
productName=5;
[locationManager startUpdatingLocation];
break;
default:
break;
}
}
I get this error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of rows in section 0. The number of rows contained in an
existing section after the update (15) must be equal to the number of
rows contained in that section before the update (15), plus or minus
the number of rows inserted or deleted from that section (0 inserted,
15 deleted) and plus or minus the number of rows moved into or out of
that section (0 moved in, 0 moved out).'
* First throw call stack: (0x2e332e83 0x3868f6c7 0x2e332d55 0x2ecdb0af 0x30c8a34d 0x30cb083f 0x6d2d9 0x30aebda3 0x30aebd3f
0x30aebd13 0x30ad7743 0x30bf7c59 0x30bf7a27 0x30cbca25 0x30aaf1a1
0x30ae69fd 0x30ae63ab 0x30abbd79 0x30aba569 0x2e2fdf1f 0x2e2fd3e7
0x2e2fbbd7 0x2e266471 0x2e266253 0x32fa02eb 0x30b1b845 0x6948d
0x38b88ab7)
Any help much appreciated!
Update the source array of tableview, No need to insert new rows.
[self.earthquakeList addObjectsFromArray:earthquakes];
[self.tableView reloadData];
If you add elements to the data source array, no. of rows in the table view will update automatically. You don't need to insert rows. Just insert elements. Implement your method as:
- (void)addEarthquakesToList:(NSArray *)earthquakes
{
[self.earthquakeList addObjectsFromArray:earthquakes];
[self.tableview reloadData];
}
erm, instead of
[self.tableView insertRowsAtIndexPaths:indexPaths
withRowAnimation:UITableViewRowAnimationAutomatic];
try
[self.tableView reloadRowsAtIndexPaths:indexPaths
withRowAnimation:UITableViewRowAnimationAutomatic];
anyways.... your code snippets are still insufficient, for me atleast, to locate your problem.
But since you said:
I want to refresh the whole table with new data when user click on the segment control in the tableview.
If i had to make a segmented tableView with 4 segments that displays different data for different segments, i'd do this:
NOTE: this is a basic example. it doesn't adhere to your specific code but should give you an idea to drop the logic of playing with indexPaths or that manual adding/removing objects from the array and instead use the tableView reloadData appropriately.
- (void)viewDidLoad
{
[super viewDidLoad];
//declare NSArray *arrFirst, *arrSecond, *arrThird, *arrFourth, *arrSelected
//in the .h file
arrFirst = [[NSArray alloc] initWithObjects:#"eins",#"zwei",#"drei",#"vier",#"fünf",#"sechs", nil];
arrSecond = [[NSArray alloc] initWithObjects:#"one",#"two",#"three",#"four",#"five",#"six",#"seven",#"eigth",#"nine", nil];
arrThird = [[NSArray alloc] initWithObjects:#"uno",#"dos",#"tres",#"cuatro",#"cinco", nil];
arrFourth = [[NSArray alloc] initWithObjects:#"ek",#"do",#"teen",#"chaar",#"paach",#"che",#"saat", nil];
arrSelected = arrFirst;
}
- (IBAction)mySegmentsAct:(UISegmentedControl *)sender
{
switch (sender.selectedSegmentIndex)
{
case 0:
arrSelected = arrFirst;
break;
case 1:
arrSelected = arrSecond;
break;
case 2:
arrSelected = arrThird;
break;
case 3:
arrSelected = arrFourth;
break;
default:
arrSelected = nil;
break;
}
[myTableView reloadData];
}
-(NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return [arrSelected count];
}
-(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];
}
cell.textLabel.text = (arrSelected)[indexPath.row];
return cell;
}
I could also do this using a single array as well, by flushing the old array contents first:
arrSelected = nil
and then put the new elements in this array (from some source using some logic)
and finally:
myTableView reloadData.
It looks like when you selected the second segment and the crash came out?
So the following codes cause the error.
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete
withRowAnimation:UITableViewRowAnimationFade];
When you use deleteRowsAtIndexPaths or insertRowAtIndexPaths, you must insure the length of the datasource matches the current row of tableView.
For example: your tableView already has 10 rows, now you need to delete 2 rows, you should remove 2 data objects from your datasource array first, then call deleteRowsAtIndexPaths.
Try these replacements, and give us some feedback later (specially if any NSAssert "crashed" your app):
- (void)addEarthquakesToList:(NSArray *)earthquakes
{
// data checking
NSAssert(self.earthQuakeList != nil, #"earthQuakeList cannot be nil!");
NSAssert(self.tableView != nil, #"tableView cannot be nil!");
NSAssert(earthquakes != nil, #"earthquakes cannot be nil!");
// relevant implementation
[self.earthquakeList addObjectsFromArray:earthquakes];
[self.tableView reloadData];
}
- (void)segmentedControlHasChangedValue
{
// data checking
NSAssert(self.earthQuakeList != nil, #"earthQuakeList cannot be nil!");
NSAssert(self.tableView != nil, #"tableView cannot be nil!");
NSAssert(fuelType != nil, #"fuelType cannot be nil!");
NSAssert(locationManager != nil, #"locationManager cannot be nil!");
// relevant implementation
switch (fuelType.selectedSegmentIndex)
{
case 0:
productName=1; // what is this?
[locationManager startUpdatingLocation];
break;
case 1:
productName=2;
[self.earthquakeList removeAllObjects];
[locationManager startUpdatingLocation];
break;
case 2:
productName=4;
[locationManager startUpdatingLocation];
break;
case 3:
productName=5;
[locationManager startUpdatingLocation];
break;
default:
break;
}
[self.tableView reloadData]; // if you want to change the data whenever the user changes the UISegmentedControl to any new value, leave this here.
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
// data checking
NSAssert(self.earthQuakeList != nil, #"earthQuakeList cannot be nil!");
NSAssert(self.tableView != nil, #"tableView cannot be nil!");
// relevant implementation
[self.earthquakeList removeAllObjects];
[self.tableView reloadData];
}
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];