Search code examples
cocoacore-datansfetchrequest

Unsorted NSFetchRequest in an unsaved managedObjectContext


I'm trying to implement a controller for my Cocoa NSTableView, which is filled with data of a SQLite database file. The controller implements the NSTableViewDataSource protocol and thus the methods

-(NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
}

and

-(id)tableView:(NSTableView*)tableView setObjectValue:(id)object
                                       forTableColumn:(NSTableColumn*)tableColumn
                                       row:(NSInteger*)row {
}

which are obviously called quiet often (e.g. if I scroll the table). To provide up-to-date data to both methods I'm executing an NSFetchRequest every time one of these methods is invoked.

The actual problem is related to my IBAction, that adds a new entry to my table. At the end of this method I call the reloadData method on the table view, which, if I'm correct, calls both protocol methods at least one time and then leads to unsorted data in the table. I've figured out that every NSFetchRequest returns this unsorted data until I save the managedObjectContext. But this is not an option (and not even possible) at this time since there is a mandatory field, which needs to be filled out in the table first.

So here are my two questions:

1) Why does my very first fetch request after the insertNewObjectForEntityForName call (and all further requests until I save) result in unsorted data?

2) How can I avoid this behaviour without saving (since I can't without the entered mandatory field)?

Since I'm new to this whole Cocoa and CoreData stuff I will post my complete code to give you a clear understanding what I'm trying to do. Any comments are welcome.

Regards,

Richard

#import "EventTabController.h"
#import "CoreData.h"
#import "Season.h"

@implementation EventTabController

-(id) init {
    if(self = [super init]) {
        managedObjContext = [[CoreData getInstance] managedObjContext];
    }

    return self;
}

/**
 * Part of the NSTableViewDataSource protocol. This method must return the number of
 * elements that are currently to be shown in the table.
 */
-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
    NSError *err;
    NSInteger numOfRows = [managedObjContext countForFetchRequest:[self createSeasonFetchRequest] error:&err];
    NSLog(@"[%@] %lu objects in table", [self class], (long)numOfRows);
    return numOfRows;
}

/**
 * Part of the NSTableViewDataSource protocol. This method must return the object for a specific cell,
 * The cell is identified by a row number and a NSTableColumn object.
 */
-(id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    NSError * err;
    NSArray* result = [managedObjContext executeFetchRequest:[self createSeasonFetchRequest] error:&err];

    Season *season = [result objectAtIndex:row];
    NSObject* obj = [season valueForKey:[tableColumn identifier]];
    NSLog(@"[%@] Index: %lu - %@", [self class], (long)row, obj);
    return obj;
}

/**
 * Part of the NSTableViewDataSource protocol. This method sets the value for an entity, that was entered in the table.
 * The value to insert is identified by a row number and a NSTableColumn object.
 */
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    NSError * err;
    NSArray* result = [managedObjContext executeFetchRequest:[self createSeasonFetchRequest] error:&err];

    Season *season = [result objectAtIndex:row];
    [season setValue:object forKey:[tableColumn identifier]];
}

/**
 * Creates a fetch request for the Season entity. The request does not include any subentities. 
 */
-(NSFetchRequest*) createSeasonFetchRequest {
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:[NSEntityDescription entityForName:@"Season" inManagedObjectContext:managedObjContext]];
    [fetchRequest setIncludesSubentities:NO];

    return fetchRequest;
}

/**
 * Called when the 'Add Season' button is pressed in the gui. It creates a new (empty) Season object
 * within the managedObjectConext and forces the season table to refresh;
 */
- (IBAction)addSeason:(id)sender {
    NSLog(@"[%@] 'Add Season' button has been pressed...", [self class]);
    [NSEntityDescription insertNewObjectForEntityForName:@"Season" inManagedObjectContext:managedObjContext];

    [seasonTable reloadData];
}

@end

Solution

  • As Tom pointed out, I missed to add an NSSortDescriptor.

    I modified my code and it works like charm.

    /**
     * Creates a fetch request for the Season entity. The request does not include any subentities. 
     */
    -(NSFetchRequest*) createSeasonFetchRequest {
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setEntity:[NSEntityDescription entityForName:@"Season" inManagedObjectContext:managedObjContext]];
        [fetchRequest setIncludesSubentities:NO];
    
        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
        [fetchRequest setSortDescriptors:@[sortDescriptor]];
    
        return fetchRequest;
    }
    

    I read this article to get a more clear understanding of how to use basic NSSort features: Apple Developer - CoreData Fetching