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];
}
Related
I am trying to add a new row to my custom cell with an NSMutablearray from another viewcontroller but I am getting an error when a new row is added. itemsTableView is visible in photoCaptureView which is the view for photoCaptureViewController. So when ScannerModalViewController (which is being called also in photoCaptureViewController) is called and capture the item/data and once it is dismissed scannedBarcode is called to add a new row to my custom cell and populate it I'm getting this error.
I am getting a warning and an error. The warning is
Warning once only: UITableView was told to layout its visible cells and other contents without
being in the view hierarchy (the table view or one of its superviews has not been added to a
window). This may cause bugs by forcing views inside the table view to load and perform layout
without accurate information (e.g. table view bounds, trait collection, layout margins, safe
area insets, etc), and will also cause unnecessary performance overhead due to extra layout
passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch
this in the debugger and see what caused this to occur, so you can avoid this action altogether
if possible, or defer it until the table view has been added to a window. Table view:
<UITableView: 0x1038a9c00; frame = (10 70; 398 794); clipsToBounds = YES; gestureRecognizers =
<NSArray: 0x28165f0c0>; layer = <CALayer: 0x2818ebb20>; contentOffset: {0, 0}; contentSize:
{398, 60}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <PhotoCaptureViewController: 0x10364a880>>
error is
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing
section after the update (3) must be equal to the number of rows contained in that section
before the update (1), plus or minus the number of rows inserted or deleted from that section
(1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section
(0 moved in, 0 moved out).
My code is photoCaptureViewController.h
#interface PhotoCaptureViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, ScannerModalViewControllerDelegate> {
PhotoCaptureView* photoCaptureView;
NSMutableArray* barcodeItems;
ScannerModalViewController* scannerModalViewController;
}
-(void) scanBarcode;
-(void) scannedBarcode:(NSString *) barcode;
#end
photoCaptureViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[photoCaptureView.itemsTableView registerNib:[UINib nibWithNibName:#"BarcodeItemsTableViewCell" bundle:nil] forCellReuseIdentifier:#"BarcodeItemsCell"];
photoCaptureView.itemsTableView.rowHeight = 60;
photoCaptureView.itemsTableView.dataSource = self;
photoCaptureView.itemsTableView.delegate = self;
[photoCaptureView.itemsTableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
}
#pragma mark scannerModalViewController Methods
-(void) scanBarcode {
NSLog(#"[%#] Scan Barcode Requested", self.class);
scannerModalViewController = [[ScannerModalViewController alloc] init];
scannerModalViewController.delegate = self;
scannerModalViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:scannerModalViewController animated:YES completion:nil];
}
-(void) scannedBarcode:(NSMutableArray *) barcodes {
barcodeItems = [[NSMutableArray alloc] init];
[barcodeItems addObjectsFromArray:barcodes];
[barcodeItems addObject:#"test"];
[barcodeItems addObject:#"test1"];
NSLog(#"%#", barcodes);
NSLog(#"%#", barcodeItems);
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:barcodeItems.count-1 inSection:0];
[photoCaptureView.itemsTableView beginUpdates];
[photoCaptureView.itemsTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[photoCaptureView.itemsTableView endUpdates];
}
#pragma mark UITableViewDataSource methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return barcodeItems.count + 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Cell Initialized");
static NSString *cellIdentifier = #"BarcodeItemsCell";
BarcodeItemsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (cell == nil) {
cell = [[BarcodeItemsTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Configure the cell...
cell.barcodeLabel.text = [barcodeItems objectAtIndex:indexPath.row];
UIImage *btnImage = [UIImage imageNamed:#"barcodeIcon"];
[cell.leftButton setImage:btnImage forState:UIControlStateNormal];
NSLog(#"Cell Populated");
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return true;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[barcodeItems removeObjectAtIndex:indexPath.row];
[photoCaptureView.itemsTableView beginUpdates];
[photoCaptureView.itemsTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[photoCaptureView.itemsTableView endUpdates];
}
}
#end
If you insert the items with insertRowsAtIndexPaths you have to specify all paths.
And beginUpdates /endUpdates` has no effect for a single insert/delete/move operation.
-(void) scannedBarcode:(NSMutableArray *) barcodes {
barcodeItems = [[NSMutableArray alloc] init];
[barcodeItems addObjectsFromArray:barcodes];
[barcodeItems addObject:#"test"];
[barcodeItems addObject:#"test1"];
NSLog(#"%#", barcodes);
NSLog(#"%#", barcodeItems);
NSMutableArray<NSIndexPath *>* indexPaths = [[NSMutableArray alloc] init];
for (i = 0; i < barcodeItems.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[photoCaptureView.itemsTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
}
And numberOfRowsInSection must return
return barcodeItems.count;
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 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 getting this error when deleting the last row in the section of a UITableView.
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (2) must be equal to the number of sections contained in the table view before the update (3), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'
My table has 3 sections IF an array has a count above 0 (users can add locations themselves). If the array is equal to 0 then the table contains only 2 section, which is where I think the problem lies, I just can't figure it out.
The code I have for deleting the cells and updating the arrays is:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* delCell = [tableView cellForRowAtIndexPath:indexPath];
delName = delCell.textLabel.text;
[self deleteLocation];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
-(void)deleteLocation {
NSLog(#"Name = %#",delName);
NSUserDefaults *customLocations = [NSUserDefaults standardUserDefaults];
NSUserDefaults *sharedPref = [NSUserDefaults standardUserDefaults];
[customLocations removeObjectForKey:delName];
[customLocations synchronize];
[customLoc removeObject:delName];
saveCustomLoc = [NSArray arrayWithArray:customLoc];
[sharedPref setObject:saveCustomLoc forKey:#"CustomLocations"];
[sharedPref synchronize];
NSLog(#"%#",saveCustomLoc);
NSLog(#"%lu",(unsigned long)[saveCustomLoc count]);
[self showAlert];
}
- (void) showAlert {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Location Deleted"
message:#"The location has been deleted"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
}
This works great until the final cell.
An example of how my table is set up is:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
if ([customLoc count] == 0) { // If there is no data in the custom locations array the don't include that section
if (section == 0) {
return [serverSelection count];
}
else {
return [qServerSelection count];
}
}
else {
if (section == 0) {
return [customLoc count];
}
else if (section == 1) {
return [serverSelection count];
}
else {
return [qServerSelection count];
}
}
So the headings/number of rows/number of sections etc. is dependent on the contents of an Array. The fact that the section the cells are being removed from disappears (or rather suddenly has 4 cells in as section 1 moves to section 0) I think is causing the issue.
Any ideas how to work around this?
After looking into this some more I found out (with the help of some Apple forum members) that when removing the last row from a section you also need to explicitly delete the section as well.
I also added table update code at the beginning and the end as shown below:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView beginUpdates];
UITableViewCell* delCell = [tableView cellForRowAtIndexPath:indexPath];
delName = delCell.textLabel.text;
[self deleteLocation];
if ([customLoc count] == 0) {
[tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationTop];
}
else {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
[tableView endUpdates];
}
So, if my array is 0 (in effect the last row is being deleted) the section itself is removed.
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.