I am having an issue (understanding issue to be honest) with NSFetchedResultsController and the new NSOrderedSet relationships available in iOS 5.
I have the following data-model (ok, my real one is not drawer's and sock's!) but this serves as a simple example:
Drawer and Sock are both NSManagedObjects in a Core Data model/store. On Drawer
the socks
relationship is a ordered to-many relationship to Sock
. The idea being that the socks are in the drawer in a specific order. On Sock
the drawer
relationship is the inverse of the socks
relationship.
In a UIViewController I am drawing a UITableView based on these entities. I am feeding the table using a NSFetchedResultsController
.
- (NSFetchedResultsController *)fetchedResultsController1 { if (_fetchedResultsController1 != nil) { return _fetchedResultsController1; } NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Sock" inManagedObjectContext:[NSManagedObjectContext MR_defaultContext]]; [fetchRequest setEntity:entity]; NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"drawer.socks" ascending:YES]; [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]]; self.fetchedResultsController1 = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_defaultContext] sectionNameKeyPath:nil cacheName:@"SocksCache"]; self.fetchedResultsController1.delegate = self; return _fetchedResultsController1; }
When I run this, I get the following errror: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
This makes sense to me, as the relationship is a NSOrderedSet
and not a single entity to compare against for sorting purposes.
What I want to achieve is for the Socks
to appear in the UITableView
in the order specified in the socks
relationship. I don't really want to have a sort order but NSFetchedResultsController
, which is a great component is insisting there has to be one. How can I tell it to use the socks order on the Drawer entity. I don't want the table to show Drawer entities at all.
Note: I am using this within an iOS5 application only, so ordered relationships are available.
Anyone that can offer me any direction, would be greatly appreciated. Thanks for your time.
Edit: So the tableview that display's socks does so for just one drawer. I just want the table view to honor the order that the socks relationship contains. I'm not sure what to set the sort criteria to make sure that happens.
Damien,
You should just make your NSFetchRequest
use the array form of the ordered set. It will operate fine. Your controller needs an attribute to sort on. Hence, you'll need to specify that too.
Andrew
I found this thread while looking for an answer to the exact question posed by the OP.
I never found any examples of presenting such data in a tableView
without adding the additional sort field. Of course, adding the sort field pretty much eliminates any benefit in using the ordered relationship. So given that I got this working, I thought it might be helpful to others with the same question if I posted my code here. It turned out to be quite simple (much simpler than using an additional sort field) and with apparently good performance. What some folks may not have realized (that includes me, initially) is that the type (NSOrderedSet
) of the "ordered, to-many" relationship attribute has a method for getting objectAtIndex
, and that NSMUtableOrderedSet
has methods for inserting, and removing objectAtIndex
.
I avoided using the NSFetchedResultsController
, as some of the posters suggested. I've used no array, no additional attribute for sorting, and no predicate. My code deals with a tableView
wherein there is one itinerary entity and many place entities, with itinerary.places
being the "ordered, to-many" relationship field. I've enabled editing/reordering, but no cell deletions. The method moveRowAtIndexPath
shows how I've updated the database with the reordering, although for good encapsulation, I should probably move the database reordering to my category file for the place managed object. Here's the entire TableViewController.m
:
// // ItineraryTVC.m // Vacations // // Created by Peter Polash on 8/31/12. // Copyright (c) 2012 Peter Polash. All rights reserved. // #import "ItineraryTVC.h" #import "AppDelegate.h" #import "Place+PlaceCat.h" #import "PhotosInVacationPlaceTVC.h" @interface ItineraryTVC () @end @implementation ItineraryTVC #define DBG_ITIN YES @synthesize itinerary ; - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if (self) { // Custom initialization } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.navigationItem.rightBarButtonItem = self.editButtonItem ; } - (void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated] ; UIManagedDocument *doc = UIAppDelegate.vacationDoc; [doc.managedObjectContext performBlock:^ { // do this in the context's thread (should be the same as the main thread, but this made it work) // get the single itinerary for this document self.itinerary = [Itinerary setupItinerary: doc ] ; [self.tableView reloadData] ; }]; } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown ); } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.itinerary.places count ]; } - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath { static NSString *CellIdentifier = @"Itinerary Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier ]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: CellIdentifier]; } Place *place = [self.itinerary.places objectAtIndex:indexPath.row]; cell.textLabel.text = place.name; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d photos", [place.photos count]]; return cell; } #pragma mark - Table view delegate - (BOOL) tableView: (UITableView *) tableView canMoveRowAtIndexPath:( NSIndexPath *) indexPath { return YES; } -(BOOL) tableView: (UITableView *) tableView canEditRowAtIndexPath: (NSIndexPath *) indexPath { return YES ; } -(void) tableView: (UITableView *) tableView moveRowAtIndexPath: (NSIndexPath *) sourceIndexPath toIndexPath: (NSIndexPath *) destinationIndexPath { UIManagedDocument * doc = UIAppDelegate.vacationDoc ; [doc.managedObjectContext performBlock:^ { // perform in the context's thread // itinerary.places is the "ordered, to-many" relationship attribitute pointing to all places in itinerary NSMutableOrderedSet * places = [ self.itinerary.places mutableCopy ] ; Place *place = [ places objectAtIndex: sourceIndexPath.row] ; [places removeObjectAtIndex: sourceIndexPath.row ] ; [places insertObject: place atIndex: destinationIndexPath.row ] ; self.itinerary.places = places ; [doc saveToURL: doc.fileURL forSaveOperation: UIDocumentSaveForOverwriting completionHandler: ^(BOOL success) { if ( !success ) NSLog(@"Error saving file after reorder, startPos=%d, endPos=%d", sourceIndexPath.row, destinationIndexPath.row) ; }]; }]; } - (UITableViewCellEditingStyle) tableView: (UITableView *) tableView editingStyleForRowAtIndexPath: (NSIndexPath *) indexPath { return ( UITableViewCellEditingStyleNone ) ; } - (void) prepareForSegue:(UIStoryboardSegue *) segue sender: (id) sender { NSIndexPath *indexPath = [self.tableView indexPathForCell: sender] ; PhotosInVacationPlaceTVC * photosInVacationPlaceTVC = segue.destinationViewController ; Place *place = [self.itinerary.places objectAtIndex:indexPath.row ]; photosInVacationPlaceTVC.vacationPlace = place ; photosInVacationPlaceTVC.navigationItem.title = place.name ; UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStylePlain target:nil action:nil]; self.navigationItem.backBarButtonItem = backButton; } @end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With