Search code examples
uitableviewcore-datansfetchedresultscontroller

Why is NSFetchedResultsController assigning fetched objects to multiple UITableView sections?


In another question of mine concerning the addition of an insert row in a UITableView backed by Core Data, I mentioned that my NSFetchedResultsController is assigning each object it fetches to a separate section in my UITableView. I assumed this was merely the default behavior, but Marcus S. Zarra said there might be something wrong with my configuration of the controller or my datasource delegate methods. I admit, my code feels a bit like Frankenstein with parts pulled from the Apple docs and numerous tutorials. This is my first program and my first time using Core Data, so please be gentle ;)

My table view controller's header is as follows:

    #import <UIKit/UIKit.h>
    #import "RubricAppDelegate.h"


    @interface ClassList : UITableViewController {
        NSMutableArray *classList;
        NSFetchedResultsController *fetchedResultsController;
        NSManagedObjectContext *managedObjectContext;

}

@property(nonatomic,retain) NSMutableArray *classList;
@property(nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property(nonatomic, retain) NSManagedObjectContext *managedObjectContext;

- (IBAction) grade:(id)sender;

@end

My implementation file includes a bunch of dummy test data. I included that in case I am using incorrect methods for instantiating the Core Data objects. Basically, I want to know if my NSFetchedResultsController should not be returning my objects (in this case, instances of myClass) into separate sections. If so, what am I doing to create that problem?

The ultimate goal at the moment is for me to be able to add an insert cell at the top of my table (I know that putting it at the bottom is "standard," but I like how it looks in the apps that do it the other way around). You will notice my -tableView:editingStyleForRowAtIndexPath: sets the cell style of section 0 to insert, but of course I need to figure out how to start the listing of myClass.classTitle at cell 1 instead of cell 0 (which is why I want to determine if having each object assigned to its own section is normal).

Here is my implementation file:

#import "ClassList.h"
#import "ClassRoster.h"
#import "RubricAppDelegate.h"
#import "Student.h"
#import "myClass.h"


@implementation ClassList

@synthesize classList;
@synthesize fetchedResultsController;
@synthesize managedObjectContext;

#pragma mark -
#pragma mark View lifecycle


- (void)viewDidLoad {
    [super viewDidLoad];

    self.editing = YES;

    RubricAppDelegate *appDelegate = (RubricAppDelegate *)[[UIApplication sharedApplication] delegate];
    managedObjectContext = [appDelegate managedObjectContext];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"myClass" inManagedObjectContext:managedObjectContext];
    NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
    [request setEntity:entity];

    //test data
    myClass *newClass = (myClass *) [NSEntityDescription insertNewObjectForEntityForName:@"myClass" inManagedObjectContext:managedObjectContext];
    newClass.classTitle = @"UFDN 1000";
    NSNumber *ID = [NSNumber numberWithInt:1];
    newClass.classID = ID;

    Student *newStudent = (Student *) [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:managedObjectContext];
    newStudent.classID = ID;
    newStudent.studentName = @"Andy Albert";
    newStudent.studentUsername = @"albera";
    [newClass addStudentsObject:newStudent];

    newStudent.classID = ID;
    newStudent.studentName = @"Bob Dole";
    newStudent.studentUsername = @"doleb";
    [newClass addStudentsObject:newStudent];

    newStudent.classID = ID;
    newStudent.studentName = @"Chris Hanson";
    newStudent.studentUsername = @"hansoc";
    [newClass addStudentsObject:newStudent];

    myClass *newClass2 = (myClass *) [NSEntityDescription insertNewObjectForEntityForName:@"myClass" inManagedObjectContext:managedObjectContext];
    newClass2.classTitle = @"UFDN 3100";
    ID = [NSNumber numberWithInt:2];
    newClass2.classID = ID;

    newStudent.classID = ID;
    newStudent.studentName = @"Danny Boy";
    newStudent.studentUsername = @"boyd";
    [newClass2 addStudentsObject:newStudent];

    newStudent.classID = ID;
    newStudent.studentName = @"James Matthews";
    newStudent.studentUsername = @"matthj";
    [newClass2 addStudentsObject:newStudent];

    newStudent.classID = ID;
    newStudent.studentName = @"Aaron Todds";
    newStudent.studentUsername = @"toddsa";
    [newClass2 addStudentsObject:newStudent];


    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"classID" ascending:YES];
    NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
    [request setSortDescriptors:sortDescriptors];
    [sortDescriptor release];
    NSError *error;

    fetchedResultsController = [[NSFetchedResultsController alloc]
                                               initWithFetchRequest:request 
                                               managedObjectContext:self.managedObjectContext
                                               sectionNameKeyPath:@"classTitle" cacheName:nil];

    [fetchedResultsController performFetch:&error];

    UIBarButtonItem *gradeButton = [[UIBarButtonItem alloc] 
                                    initWithTitle:@"Grade" 
                                    style:UIBarButtonItemStylePlain
                                    target:self
                                    action:@selector(grade:)];
    self.navigationItem.rightBarButtonItem = gradeButton;

    [gradeButton release];

}

- (IBAction) grade:(id)sender {

}

#pragma mark -

#pragma mark Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    NSLog(@"Number of sections = %d", [[fetchedResultsController sections] count]);
    return ([[fetchedResultsController sections] count]);

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    id <NSFetchedResultsSectionInfo> myClass = [[fetchedResultsController sections] objectAtIndex:section];
    NSLog(@"Number of classes = %d", [myClass numberOfObjects]);

    return [myClass numberOfObjects];

}

// Customize the appearance of table view cells.
- (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];

        myClass *theClass = [fetchedResultsController objectAtIndexPath:indexPath];
        NSLog(@"Class name is: %@", theClass.classTitle);
        cell.textLabel.text = theClass.classTitle;
    }

    return cell;
}

    - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (indexPath.section == 0) {
            return UITableViewCellEditingStyleInsert;
        }
        else return UITableViewCellEditingStyleDelete;
    }


    // Override to support editing the table view.
    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

        if (editingStyle == UITableViewCellEditingStyleDelete) {
            myClass *result = (myClass *)[fetchedResultsController objectAtIndexPath:indexPath];
            [managedObjectContext deleteObject:result];
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];

        }   
        else if (editingStyle == UITableViewCellEditingStyleInsert) {

        }   
    }

    #pragma mark -
    #pragma mark Table view delegate

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        // Navigation logic may go here. Create and push another view controller.
        /*
         <#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:@"<#Nib name#>" bundle:nil];
         // ...
         // Pass the selected object to the new view controller.
         [self.navigationController pushViewController:detailViewController animated:YES];
         [detailViewController release];
         */
    }


    #pragma mark -
    #pragma mark Memory management

    - (void)didReceiveMemoryWarning {
        // Releases the view if it doesn't have a superview.
        [super didReceiveMemoryWarning];

        // Relinquish ownership any cached data, images, etc that aren't in use.
    }

    - (void)viewDidUnload {
        // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
        // For example: self.myOutlet = nil;
    }


    - (void)dealloc {
        [classList release];
        [super dealloc];
    }


    @end

My RubricAppDelegate is essentially identical to the Apple documentation for setting up Core Data NSManagedObjectContext, NSPersistentStoreCoordinator, etc. However, if you think there might be a problem in there, just let me know and I'll post it.

Edit: I forgot to mention two reasons I know each object is being assigned to a different section.

1) NSLog(@"Number of sections = %d", [[fetchedResultsController sections] count]); inside of -numberOfSectionsInTableView: returns the number of myClass objects I have.

2) If I set -numberOfSectionsInTableView: to return 1, my table only displays one object and cuts the rest out.


Solution

  • You have sections because you tell the fetched results controller to create them by passing a non-Nil value for sectionNameKeyPath: when you initialize the FRC.

    Change:

    fetchedResultsController = [[NSFetchedResultsController alloc]
                                               initWithFetchRequest:request 
                                               managedObjectContext:self.managedObjectContext
                                               sectionNameKeyPath:@"classTitle" cacheName:nil];
    

    ...to:

    fetchedResultsController = [[NSFetchedResultsController alloc]
                                               initWithFetchRequest:request 
                                               managedObjectContext:self.managedObjectContext
                                               sectionNameKeyPath:nil cacheName:nil];
    

    ... and the sections will go away. Otherwise, the FRC will create one section for each value of the classTitle attribute in the store. If each myClass instance has a different value for classTitle each instance will have its own section in the tableview.