What I want to do is pretty simple. In my UITableViewController, I want to load data from multiple NSFetchedResultControllers (I have multiple entities in my data model) and put data from each one into a different section in the table view. So for example, all the fetched items from the first NSFetchedResultController would go in section 0 in the UITableView, the fetched items from the other one goes into section 1, etc.
The Core Data template p开发者_如何学Pythonroject doesn't demonstrate how to do this. Everything (mainly the index paths) is coded without taking sections into account (there are no sections in the default template) and everything is taken from a single NSFetchedResultController. Are there any example projects or documentation that demonstrates doing this?
Thanks
Assume for a moment the following in your header (code below will be slightly sloppy, my apologies):
NSFetchedResultsController *fetchedResultsController1; // first section data
NSFetchedResultsController *fetchedResultsController2; // second section data
Let the table know you want to have 2 sections:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2; // you wanted 2 sections
}
Give it the section titles:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [NSArray arrayWithObjects:@"First section title", @"Second section title", nil];
}
Let the table know how many rows there are per sections:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return [[fetchedResultsController1 fetchedObjects] count];
} else if (section == 1) {
return [[fetchedResultsController2 fetchedObjects] count];
}
return 0;
}
Build the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
... // table cell dequeue or creation, boilerplate stuff
// customize the cell
if (indexPath.section == 0) {
// get the managed object from fetchedResultsController1
// customize the cell based on the data
} else if (indexPath.section == 1) {
// get the managed object from fetchedResultsController2
// customize the cell based on the data
}
return cell;
}
Multiple fetch controllers (and possibly multiple entities) is the wrong approach. The correct solution is to use the sectionNameKeyPath
param to the NSFetchedResultController
to group the results into multiple sections. If you think about your entities differently perhaps they are actually the same entity and instead you can use a property itemType
which you can then section on (and you must also sort on it too). E.g. say I had entities Hops and Grains then I could change those to Ingredient and have a int_16 property ingredientType
which I then have an enum in code to store the values hopType = 0
, grainType = 1
. After all the ingredient is just a name and a weight, which both of these share.
If however your entities really have a distinct set of properties, then the correct solution is to create a parent abstract entity that has a property that you can use to section, e.g. sortOrder
, sectionID
or type
. When you then create a fetch controller and fetch the abstract parent entity, you actually get results containing all of the sub-entities. E.g in the Notes app they have an abstract parent entity NoteContainer
that has child entities Account
and Folder
. This enables a single fetch controller to display the account in the first cell in the section, and then have all the folders in the following cells. E.g. All iCloud Notes (is actually the account), then Notes (is the default folder), followed by all the custom folders, then the trash folder. They use a sortOrder
property and the default folder is 1
, the custom folders are all 2
, and the trash is 3
. Then by adding this as a sort descriptor they can have the cells display in the order they want. It's a bit different from your requirement because they have the 2 entities mixed into different sections, but you can still make use of it just with different sort properties.
The moral of the story is don't fight the framework, embrace it :-)
Extending Giao's solution with two NSFetchedResultsControllers - we have to remember our NSFetchedResultsController do not know about our two sections and returned NSIndexPathes will be always for first section.
So when we are getting an object in cell configuration:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
[tableView registerNib:[UINib nibWithNibName:@"cell" bundle:nil] forCellReuseIdentifier:@"cell"];
cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
-(void)configureCell:(UITableViewCell*)cell atIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
NSManagedObject *object = [self.fetchedResults1 objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
//use object to configure cell
} else {
NSManagedObject *object = [self.fetchedResults2 objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
//use object to configure cell
}
}
Updating cells while NSFetchedResultsController noticed some changes:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
NSIndexPath *customIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:(controller == self.fetchedResultsController1)?0:1];
NSIndexPath *customNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:(controller == self.fetchedResultsController2)?0:1];
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:customNewIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:customIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:customIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:customNewIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
精彩评论