First row is not shown after insert operation, save my hair

I have a situation here and losing my hair...

Very simple application driven by core data which has table view controller with add navigation button. It is very similar to Recipes sample, with couple differences

  • There is only single Entity in model with 2 attributes
  • The cell in main table is not customized, using default textLabel

The core data part is fine, since the new entry is added to underlying storage and retrieved using fetched results controller. The problem comes when I add new item which will be placed on the top of the list. This can be for instance new item on empty list or item with sort order taking it to the top. The item which is placed on top is not visible! The cell is there, with nil text label, however, I clearly saw the fetched results controller issued update notification and I configured cell, updating with new text.

The only way I can make that cell to be shown is scroll data table to invoke cellForRowAtIndexPath, then the item text is updated. I add another item which will be placed on top, again the new item text is not visible. I add some item which will be residing on bottom - no problem, the text in cell is visible. Also initially when I just start application there is no problem, all items are visible.

Not sure anyone can help to solve the mistery without the source code, but still hope to get some hints on how to debug... I tried to compare the delegate invocations with Receipe application and all is same.

UPD:

- (void)configureCell:(UITableViewCell *)cell 
        atIndexPath:(NSIndexPath *)indexPath 
{
    Word* word = (Word*)[fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = word.word;
    printf("configureCell: %d : \"%s\"\n", indexPath.row,
           [word.word cStringUsingEncoding:NSASCIIStringEncoding]);
}

- (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] autorelease];
    }

    [self configureCell:cell atIndexPath:indexPath];

    return cell;
}

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
{
[self.tableView beginUpdates];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{
[self.tableView endUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;

switch(type) {
          case NSFetchedResultsChangeInsert:
        printf("didChangeObjectAtRow: %d: insert\n", newIndexPath.row);
        [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeDelete:
        printf("didChangeObjectAtRow: %d : delete\n", indexPath.row);
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeUpdate:
        printf("didChangeObjectAtRow: %d : update\n", indexPath.row);
        [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
        break;

    case NSFetchedResultsChangeMove:
        printf("didChangeObjectAtRow: %d / %d : move\n", indexPath.row, newIndexPath.row);
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;
}
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{
        NSInteger count = [[fetchedResultsController sections] count];

        if (count == 0) 
        {
            count = 1;
        }
printf("number of sections: %d\n", count);
return count;
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
        {
        NSInteger numberOfRows = 0;
        if ([[fetchedResultsController sections] count] > 0) 
{
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    numberOfRows = [sectionInfo numberOfObjects];
}

printf("number of rows in sections: %d is %d\n", section, numberOfRows);
return numberOfRows;
}

- (NSFetchedResultsController*)fetchedResultsController
{
if(fetchedResultsController != nil)
    return fetchedResultsController;

// This is autoreleased as the name implies
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Word" inManagedObjectContext:managedObjectContext];

NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];

NSSortDescriptor* sortByWordDescriptor = [[NSSortDescriptor alloc] initWithKey:@"word" ascending:YES];
NSArray* sortArray = [[NSArray alloc] initWithObjects:sortByWordDescriptor, nil];
[fetchRequest setSortDescriptors:sortArray];


NSFetchedResultsController* controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"Hmm"];
controller.delegate = self;
self.fetchedResultsController = controller;

[fetchRequest release];
[sortByWordDescriptor release];
[sortArray release];
[controller release];

return fetchedResultsController;
}

- (void) add:(id)sender
{
WordzWordEditView *wordEditViewController = [[WordzWordEditView alloc] initWithNibName:@"WordzWordEditView" bundle:nil];
wordEditViewController.delegate = self;

Word* word = [NSEntityDescription insertNewObjectForEntityForName:@"Word" inManagedObjectContext:self.managedObjectContext];
wordEditViewController.word = word;

UINavigationController *editWordNavigationController = [[UINavigationController alloc] initWithRootViewController:wordEditViewController];

[self presentModalViewController:editWordNavigationController animated:YES];

[wordEditViewController release];
[editWordNavigationController release];

}

UPD: output

controllerWillChangeContent
didChangeObjectAtRow: 0: insert
controllerDidChangeContent
number of sections: 1
number of sections: 1
number of rows in sections: 0 is 2
cellForRowAtIndexPath: 0
configureCell: 0 : "(null)"
post configuring: "(null)"
controllerWillChangeContent
configureCell: 0 : "asd"
didChangeObjectAtRow: 0 : update with 'asd'
controllerDidChangeContent
number of sections: 1
number of sections: 1
number of rows in sections: 0 is 2

Answers


Without the source code it is difficult to pinpoint, when you are adding the data are you calling

– insertRowsAtIndexPaths:withRowAnimation:

If you call [self.tableview reloadData] after you add it, does it appear


I would change:

case NSFetchedResultsChangeUpdate:
    printf("didChangeObjectAtRow: %d : update\n", indexPath.row);
    [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
    break;

to:

case NSFetchedResultsChangeUpdate:
    printf("didChangeObjectAtRow: %d : update\n", indexPath.row);
    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    break;

I'm not sure why the recipes app overrode that behavior, as configureCell still gets called.


Have you tried [self.tableView reloadData] within -viewWillAppear:animated ? I had similar problem with first row. But after I add this everything is Ok.


You are casting to the wrong type. (copy/paste error) You are using UITableViewCell in tableView:cellForRowatIndexPath

case NSFetchedResultsChangeUpdate:
    printf("didChangeObjectAtRow: %d : update\n", indexPath.row);
    [self configureCell:(RecipeTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
    break;

Replace:

[self configureCell:(RecipeTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];

With:

[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];

Some observations:

This line in the action method:

Word* word = [NSEntityDescription insertNewObjectForEntityForName:@"Word" inManagedObjectContext:self.managedObjectContext];

...triggers:

case NSFetchedResultsChangeInsert:
    printf("didChangeObjectAtRow: %d: insert\n", newIndexPath.row);
    [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
    break;

... before the dialog completes and before the Word managedObject has a value for its word attribute. This creates the null populated cell.

(I logged the methods in the Recipie app and got:

2010-07-07 18:56:08.848 Recipes[53880:207] NSFetchedResultsChangeInsert:=<NSIndexPath 0x11286a0> 2 indexes [0, 0]
2010-07-07 18:56:22.872 Recipes[53880:207] NSFetchedResultsChangeUpdate=<NSIndexPath 0x11286a0> 2 indexes [0, 0]

... in sequence.)

Interestingly enough, you don't log a NSFetchedResultsChangeInsert. Which makes me think something is missing. Possibly in your logging.

I think the problem might actually be caused by using a modal controller used to change the the Word.word value. The modal presentation prevents the table from responding to the NSFetchedResultsChangeUpdate when it occurs so it keeps the initial null value until you scroll which causes it to reload the cell from the fetched results controller. Just a guess.

You could test by inserting a Word object and then changing the word property in code without the modal view. Perhaps use a timer to give a little lag. If that works, then its definitely the modal view causing the problem.

If not I think you should take care to log the entire index path object to make sure you don't have some section wackiness going on.


I had a similar problem and started looking for a solution and came across this old post. I didn't see that the issue was resolved, so I took a look at what was being called in the method

  • (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath

It appears that when you come back from the detail controller, instead of performing an insert, it actually is performing an update. This has to do with the sequence that the core data recipes application has you perform the creation of the item added to the database - prior to calling the detail controller.

By adding the following line in the NSFetchedResultsChangeUpdate, I was able to get the first row to appear

        [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];

Need Your Help

A jquery plugin post toolbar

jquery-plugins

Does anyone knows about a jquery post's toolbar plugin? I have a forum and I would like to attach to each post a complete toolbar : reply, delete, send by mail, attach a file...

Fade effect attribute/function in javascript custom script?

javascript html css html5 css3

I need to add a fade effect on my javascript function