NSIndexPath has no row value. Works flawless in iOs 4.3 but not in 6.0 - ios4

I'm facing a strange problem with my UITableViewController and working with NSIndexPaths. In my UITableView I've implemented a custom OptionCell that fades in below the clicked cell and fades out afterwards. With iOs 4.3 it works flawlessly.
I've recently updated to the new xCode and iOs 6. I changed the relevant points in the project and everything works except of a crash that appears whenever I try to remove the customCell.
It crashes with:
Assertion failure in -[NSIndexPath row]
[...]
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid index path for
use with UITableView. Index paths passed to table view must contain
exactly two indices specifying the section and row. Please use the
category on NSIndexPath in UITableView.h if possible.
I've made some research regarding this error. I found this question, checked the mentioned steps but it didn't help.
While debugging I found out it crashes everytime I check the row of the indexPathToDelete. It is not NIL and should have one (works on old iOs Version). Did they change something with the NSIndexPath in between the versions? I've checked everything I could possible think of...
I marked the error in the following code segment:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
/* if user tapped the same row twice get rid of the OptionCell */
if([indexPath isEqual:self.tappedIndexPath]){
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
/* update the indexpath if needed...*/
indexPath = [self modelIndexPathforIndexPath:indexPath];
/* pointer to delete the control cell */
NSIndexPath *indexPathToDelete = self.controlRowIndexPath;
/* if same row is tapped twice => clear tapping trackers */
if([indexPath isEqual:self.tappedIndexPath]){
self.tappedIndexPath = nil;
self.controlRowIndexPath = nil;
}
/* otherwise update them appropriately */
else{
self.tappedIndexPath = indexPath; /* the row the user just tapped. */
/* Now set the location of where I need to add the option cell */
self.controlRowIndexPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
}
/* all logic is done, lets start updating the table */
[tableView beginUpdates];
/* lets delete the control cell, either the user tapped the same row twice or tapped another row */
if(indexPathToDelete){
/* CRASH APPEARS HERE!: */
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete]
withRowAnimation:UITableViewRowAnimationRight];
}
/* lets add the new control cell in the right place */
if(self.controlRowIndexPath){
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:self.controlRowIndexPath]
withRowAnimation:UITableViewRowAnimationLeft];
// [self.tableView scrollToRowAtIndexPath:self.controlRowIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
/* and we are done... */
[tableView endUpdates];
} else {
if (self.controlRowIndexPath) {
indexPath = [self modelIndexPathforIndexPath:indexPath];
NSIndexPath *indexPathToDelete = self.controlRowIndexPath;
self.tappedIndexPath = nil;
self.controlRowIndexPath = nil;
if(indexPathToDelete){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete]
withRowAnimation:UITableViewRowAnimationFade];
}
}
VerwaltungViewController* verwaltungViewController;
verwaltungViewController = [[VerwaltungViewController alloc] init];
[self.navigationController pushViewController:verwaltungViewController animated:YES];
}
}
EDIT: modelIndexPathforIndexPath:
- (NSIndexPath *)modelIndexPathforIndexPath:(NSIndexPath *)indexPath
{
int whereIsTheControlRow = self.controlRowIndexPath.row;
if(self.controlRowIndexPath != nil && indexPath.row > whereIsTheControlRow)
return [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0];
return indexPath;
}

Related

UITableViewCell with UICollectionView

I'm following this tutorial Creating a Scrolling Filmstrip Within a UITableView
but instead of using static array i'm using core data with nsfetchresultscontroller i have a problem with delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
i want to return only one item per cell which contains all items inside:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ContainerCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ContainerCell"];
NSArray *items = [[[self.frc sections] objectAtIndex:indexPath.section] objects];
[cell setCollectionData:items];
return cell;
}
but i'm getting this error:
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 1 from section 9 which only contains 1 rows before the update with userInfo (null)
if i'm using
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger rows = [super tableView:tableView numberOfRowsInSection:section];
return rows;
}
the app is not crashing but i'm getting duplicates per cell
any ideas how to fix this
Thanks
You are using non-standard numbers of rows in your sections, so you have to make sure that when the underlying data changes, the appropriate number of rows is deleted and added. In your case, none.
Look for the delegate method in your controller where the rows are erroneously deleted or added. It is mentioned in your error message and should be
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
As you can see, in the delete case, it does this:
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
But the indexPath is not describing the position of your managed object in the table view because you are displaying all rows of a section in a single row. So you can simply delete this line and replace it with the appropriate reload function for the first row in the section. Maybe you have a configureCell method as in the Xcode boilerplate code:
NSIndexPath *row = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[tableView configureCell:[tableView cellForRowAtIndexPath:row] atIndexPath:row];

NSFetchedResultsController: problems when having two table rows for one object

I've got a tableView where each data set has two rows, therefore my datasource-methods look like the following:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.fetchedResultsController.sections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex {
id <NSFetchedResultsSectionInfo> section = self.fetchedResultsController.sections[sectionIndex];
return section.numberOfObjects * 2;
}
The NSFetchedResultsControllerDelegate method:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if (NSFetchedResultsChangeDelete == type) {
NSIndexPath *cellIndexPath = indexPath;
NSIndexPath *separatorIndexPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
[self.transactionTableView deleteRowsAtIndexPaths:#[cellIndexPath, separatorIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
} else if (NSFetchedResultsChangeInsert == type) {
NSIndexPath *cellIndexPath = newIndexPath;
NSIndexPath *separatorIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row + 1 inSection:newIndexPath.section];
[self.transactionTableView insertRowsAtIndexPaths:#[cellIndexPath, separatorIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
} else {
[self.transactionTableView reloadData];
}
}
If I only use one row for each dataset, everything works as expected. But as soon as I make it this way, I get NSAssertionErrors here:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates];
}
Isn't it possible to use two rows for one data set? What am I doing wrong? I would have expected to work it this way as I always insert two indexPaths whenever the underlying core data changes.
That should definitely be possible, but your mapping from the FRC index path to the
table view index path looks wrong. It should probably be
NSIndexPath *cellIndexPath = [NSIndexPath indexPathForRow:(2*indexPath.row) inSection:indexPath.section];
NSIndexPath *separatorIndexPath = [NSIndexPath indexPathForRow:(2*indexPath.row + 1) inSection:indexPath.section];

Core data inserting rows and saving issue

I´m having some problems when saving data in core data and also with the rows organization and for better understanding of what my problem is, i´m going to explain what i have:
I have a main tableview working with dynamic rows, in this tableview i have a + button, whenever the + button is pressed, a tableview appears inside a popover were the user can choose the "type of cell" to insert in the main tableview. The "type of cell" are custom cells and they have they´re one class and xib file. Each custom cell has various textfields...so the idea is:
choose a type of cell and insert in the main tableview.
fill the textfields with data.
the data saved corresponds to the number of rows inserted and the data in the textfields.
When calling the popover tableview i have this method in my main tableview:
- (IBAction)Add:(id)sender
{
SelectProduct *typeProduct=[[self.storyboard instantiateViewControllerWithIdentifier:#"selectTable"]initWithTableViewTag:self.tableView.tag];
self.popover=[[UIPopoverController alloc]initWithContentViewController:typeProduct];
[popover presentPopoverFromBarButtonItem:buttonAdd permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
typeProduct.popView = self.popover;
typeProduct.cellSelected = self.cellSelected; //cellSelected is core data subclass.
typeProduct.delegate = self;
typeProduct.managedObjectContext = self.managedObjectContext;
}
then in my didSelectRow of my popover tableview, i have:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
row = indexPath.row;
if (row == 0)
{
cellSelected=[NSEntityDescription insertNewObjectForEntityForName:#"CellSave" inManagedObjectContext:self.managedObjectContext];
cellSelected.nameCellData = #"Olive";
cellSelected.amountData = myCostumCell.amount.text;
}
From here, a cell is inserted in my main tableview, here´s my main tableview relevant methods:
- (void)viewDidLoad
{
[self.tableView registerNib:[UINib nibWithNibName:#"myCostumCellXib" bundle:nil] forCellReuseIdentifier:#"myCostumCell"];
AppDelegate *appDelegate =[[UIApplication sharedApplication] delegate];
self.managedObjectContext=[appDelegate managedObjectContext];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self fetchedResultsController];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"myCostumCell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.cellSelected = self.cellSelected;
cell.managedObjectContext = self.managedObjectContext;
if (cell == nil)
{
cell = [[MyCostumCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cellSelected = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.nameCell.text = cellSelected.nameCellData;
if ([cellSelected.nameCellData isEqualToString:#"Olive"])
{
cell.amount.text = cellSelected.amountData;
// i have more textfields to assign but i think you understand the rest..
}
}
My fetchedResultsController method: ( also have the others but they are the standard stuff)
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
// Create and configure a fetch request.
NSFetchRequest *fetchRequestCellSave = [[NSFetchRequest alloc] init];
NSEntityDescription *entityCellSave=
[NSEntityDescription entityForName:#"CellSave" inManagedObjectContext:self.managedObjectContext];
[fetchRequestCellSave setEntity:entityCellSave];
// Create the sort descriptors array.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"nameCellData" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequestCellSave setSortDescriptors:sortDescriptors];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequestCellSave managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"nameCellData" cacheName:nil];
_fetchedResultsController.delegate = self;
self.fetchedResultsController = _fetchedResultsController;
return _fetchedResultsController;
}
Now if i want to exit the main tableview and go to another tableview, i understand that i have to save the content of the textfields in my managedObject, so:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
cellSelected.amountData = cell.amount.text;
//i have more textfields to assign but for the example i think you understand
what i want.
[self.managedObjectContext save:nil];
}
From here, one row is "saved" and the text in the amount also...but the problems start when i add one more row:
Why the new row appears on top of the tableview, instead of after the previous row inserted?
When i fill the textField (amount) of the second row inserted...exit the tableview and come back...the textfield doesn´t appears filled..What am i doing wrong?
The previous issue happens if i insert 2 rows at once, but if i insert one...exit the tableview and then come back and insert another row...the textfield amount is saved...
Where´s my problem? is it in my custom cell class? Where?...I´m sorry for the long post, but this is driving me crazy...
thanks for your time
Regards
You need to save the data as soon as it is entered, not just when the view is removed from display. As soon as a cell is scrolled off screen it will be reused or killed so you have to save your text before that happens. Best place is in the text field delegate callback when the text is changed.
When you add 2 rows before saving you have corrupted your internal state (when the second row is added but you haven't yet saved the data from the first row).
Your rows are sorted by entered text so it depends on the text (or lack of) to determine where it appears on screen.
You probably shouldn't be giving the cell a reference to the managed object context (not MVC).
You should also think about the difference between local and instance variables as your code seems to confuse them...

Multiple index paths

I need to implement two different index paths on my tableview in cellForRow at index path... One with data from my fetched results controller and another for simple text.
What is the best way to go about this.
Im not exactly sure what your asking, but it sounds like you want a table with one section containing results from an NSFetchedResultController, and another section with data from some other source. This is easy to do. If your data can doesn't need to be in multiple sections then it's very easy, just get data from fetchedResultController for section 0, and something else for section 1.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if(section == 0){
id sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}else{
return self.someArray.Count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexpath.section == 0){
static NSString *CellIdentifier = #"data cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSObject *foo = [self.fetchedResultController objectAtIndexPath:indexPath];
//configure cell
return cell;
}else{
static NSString *CellIdentifier = #"some other cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//configure cell
return cell;
}
}

Randomly crashing UITableView

I have a UITableView with a Custom Cell, (includes a uiimage generated from a website) and when I select a row it takes me to a detail view. Now if I click on a row as soon as the view loads, sometimes the app will crash. Sometimes when I return to the main tableview from the detail view the app will crash. I am not going to paste my code yet because I honestly have no idea what I would even need to post.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
selectedItems = [[stories objectAtIndex: storyIndex] objectForKey: #"title"];
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:[NSBundle mainBundle]];
dvController.imageArray = images;
dvController.selectedItems = selectedItems;
dvController.indexpath = storyIndex;
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
dvController = nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"CustomCell"owner:self options:nil];
cell = customCell;
self.customCell = nil;
}
// Configure the cell.
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
cell.title.text = [[stories objectAtIndex: storyIndex] objectForKey: #"title"];
[cell.webview loadHTMLString:[NSString stringWithFormat:#"<html><body>%#</body></html>", [images objectAtIndex:indexPath.row]] baseURL:nil];
//NSLog(#"%#", [images objectAtIndex:indexPath.row]);
return cell;
}
The NSLog report says
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CALayerArray bannerView:didFailToReceiveAdWithError:]: unrecognized selector sent to instance 0x1bcd70'
The didFailToReceiveAdWithError method is below
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
if (self.bannerIsVisible)
{
[UIView beginAnimations:#"animateAdBannerOff" context:NULL];
// banner is visible and we move it out of the screen, due to connection issue
banner.frame = CGRectOffset(banner.frame, 0, -50);
[UIView commitAnimations];
self.bannerIsVisible = NO;
}
}
The error you're getting...
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CALayerArray bannerView:didFailToReceiveAdWithError:]: unrecognized selector sent to instance 0x1bcd70'
...is fairly clear. It's nothing to do with the UITableView and everything to do with the fact that you've not implemented the bannerView:didFailToReceiveAdWithError: method in your ADBannerViewDelegate.

Resources