NSFetchedResultContoller can do that? - uitableview

Need some advice into: Can NSFetchedResultController do this?
UITableView:
[section name] <= {entity: Section, value: title}
[cell title] <= {entity: Cell, value: title}
Model:
[entity: Section, properties: title] <->> [entity: Cell, properties: title, imgPath]
Trouble:
Count of sections, and their titles is working, can't fetch object from relationship to Cell
Thanks for help...

That should be possible. In fact I think that you can use the "standard" table view data source methods and fetched results controller delegate methods if you create the FRC with the sectionNameKeyPath set to "section.title":
// Fetch "Cell" entities:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Cell"];
// First sort descriptor for grouping the cells into sections, sorted by section title:
NSSortDescriptor *sort1 = [NSSortDescriptor sortDescriptorWithKey:#"section.title" ascending:YES];
// Second sort descriptor for sorting the cells within each section:
NSSortDescriptor *sort2 = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObjects:sort1, sort2, nil];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:#"section.title"
cacheName:nil];
(I have assumed that you have an inverse relationship section from the Cell entity to the Section entity.)

Related

Core data don't return full objects, data: <fault>

I'm trying to fetch two entities eg: Student with email attribute and Professor, both of have same parent entity eg: Person with attributes entityId, firstName and lastName i want to generate them in two sections using NSFetchedResultsController.
Here is a part from getter for fetchedResultsController
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setFetchBatchSize:20];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSEntityDescription *description = [NSEntityDescription entityForName:#"Person"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:description];
NSSortDescriptor *firstNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"firstName" ascending:YES];
NSSortDescriptor *lastNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:YES];
[fetchRequest setSortDescriptors:#[firstNameDescriptor, lastNameDescriptor]];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"entityId"
cacheName:nil];
All Students have the same entityId and all Professors too
In tableView I have two prototype cells one for Student and another for Professor.
I get two sections as expected but students are in different sections, i have printed all objects from fetchedResultsController in console like this, po [self.fetchedResultsController objectAtIndexPath: [NSIndexPath indexPathForItem:1 inSection:1]] all professors are printed with fault:
<Professor: 0x6080000befc0> (entity: Professor; id: 0xd0000000001c0002 <x-coredata://03A3ECAD-CCA7-424E-86F9-258D25372BA1/Professor/p7> ; data: <fault>)
I have forced the fetch request to return full objects using [request setReturnsObjectsAsFaults:NO] but it had no effect.
Why is it happening so?
To avoid "data fault" issue you should set this field of NSFetchRequest:
request.returnsObjectsAsFaults = NO;
To separate students from professors in two sections you can use multiple NSFetchedResultsControllers, as described here:
https://stackoverflow.com/a/2309855/1689376
To avoid duplication of your code, just create a method like this and call it twice:
- (NSFetchedResultsController) createFetchedResultsController: (NSString *) entityName {
//move your code here
}

iOS CoreData NSFetchedResultsController sections and sorting

I currently have a CoreData entity that has a name and date attributes, and I would like to create a NSFetchedResultsController that returns the results sectioned by name sorted by date descending (both the sections and its contents), and if possible, only one entry per section. I prefer not to use NSDictionaryResultType.
Lets say I have the following entries:
Name | Date (year/month/day)
-----+----------------------
Anne | 2014/01/16
John | 2014/01/17
John | 2014/01/15
Nick | 2014/01/13
Nick | 2014/01/10
For the above data I wish to obtain only the following results:
Section | Entry Date
--------+-----------
John | 2014/01/17
Anne | 2014/01/16
Nick | 2014/01/13
How do I create the NSFetchedResultsControllerto obtain only the data and in the order listed above?
As of now I have the following code:
NSManagedObjectContext *moc = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"my predicate" argumentArray:...]];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:moc
sectionNameKeyPath:#"name"
cacheName:nil];
The above code sections the data by name, but each section doesn't have its entries sorted by dateAND it has entries that are not from that name!
The result that I want is the table showing unique name sorted by date like showed in the beginning of the post.
Update: I've been unable to do this, so I switched to result type dictionary and used group by for now, I will leave the question open in the hopes someone knows how to do it.
There is no way (that I am aware of) to limit each section to one result in the NSFetchedResultsController.
But, you might not have to do this. You could probably get away with not displaying everything that is not in the first section.
Also, you should be aware that you should not section the request by using any other key than the key used in the primary sort descriptor
If you are using this with a tableView you could try something like this:
FRC creation
NSSortDescriptor *sdName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSSortDescriptor *sdDate = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
// sort by name, then sort by date
[fetchRequest setSortDescriptors:#[sdName, sdDate]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:moc
sectionNameKeyPath:#"name"
cacheName:nil];
// fetch...
and the tableViewDataSource:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSFetchedResultsController *fetchedResultsController = [self fetchedResultsController];
return [fetchedResultsController.sections count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1; // only display one result for each section
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSFetchedResultsController *fetchedResultsController = [self fetchedResultsController];
YourObject *object = [fetchedResultsController objectAtIndexPath:indexPath];
... configure cell ...
Depending on how many objects you have per person this might not be feasible. If there are a lot of entries you won't display I would recommend to normalize the core data model. E.g.:
You then would display a list of Persons. But maybe this normalization would be a good idea regardless of the number of objects per name. Fetching more objects than are displayed smells fishy to me.
Here is a snippet of some code, in regards to my comment
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"EntityThingO"];
NSSortDescriptor *sortDescriptorDate = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSSortDescriptor *sortDescriptorName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
fetchRequest.sortDescriptors = #[sortDescriptorDate , sortDescriptorName];
[fetchRequest setFetchBatchSize:10];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:#"date"
cacheName:nil];

Ordering core data objects inside a table view section

I am using NSSortDescriptors to order core data objects and create table view sections depending on a date attribute. Using this kind of SortDescriptor, the table view sections are ordered as expected, but the rows inside the sections are also ordered by the same date attribute. Is there a way to have another ordering system inside each section? I guess the main problem is that core data stores date objects with date+time values, that way there are no objects with exactly the same date value. But I would need to order the objects inside the same section based on another attribute.
Thank you.
And here is my code for the NSFetchedResultsController:
-(NSFetchedResultsController*)fetchedResultsController{
if (_fetchedResultsController != nil){
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSManagedObjectContext *context = self.managedObjectContext;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"itemDate" ascending:YES];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc]initWithKey:#"itemName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor,sortDescriptor1, nil];
fetchRequest.sortDescriptors = sortDescriptors;
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"sectionIdentifier" cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
EDITED QUESTION
This is the piece of code added to the cellForRowAtIndexPath method:
int indexpathsection = indexPath.section;
[self sortedSectionForIndex:indexpathsection];
NSLog(#"INDEXPATH= %ld", (long)indexPath.section);
And this is the method proposed y Marcus:
- (NSArray*)sortedSectionForIndex:(NSInteger)index
{
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"priority" ascending:YES];
id section = [[self fetchedResultsController] sections][index];
NSLog(#"INDEX********* = %ld", (long)index);
NSArray *objects = [section objects];
NSArray *sorted = [objects sortedArrayUsingDescriptors:#[sort]];
return sorted;
}
The NSFetchedResultsController is meant for the sort order to be controlled via a single sort or set of sorts. It is not intended to do what you are trying to do.
But you can do it, it is just not as clean.
First, the discussion of sorting a section. This does not require you to write your own sorting algorithm:
- (NSArray*)sortedSectionForIndex:(NSInteger)index
{
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"MyKey" ascending:YES];
id section = [[self fetchedResultsController] sections][index];
NSArray *objects = [section objects];
NSArray *sorted = [objects sortedArrayUsingDescriptors:#[sort]];
return sorted
}
With that method, you can ask for a section to be sorted whenever you want to retrieve an object. However that is inefficient as you are sorting every time you want to touch an object which in a UITableViewController is a LOT.
Instead, take this example and integrate it with your NSFetchedResultsController and its delegate methods so that you are storing the sorted arrays and keeping them in sync. That way you are only doing the sort when the data changes instead of on each method call.
Update
The sort code I provided to you does not sort what is inside of the NSFetchedResultsController. It takes what is in the section, sorts it and returns you the sorted array. So you need to use what it returns:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *sorted = [self sortedSectionForIndex:[indexPath section]];
id object = [sorted objectAtIndex:[indexPath row]];
id cell = ...; //Now get your cell reference
//Now update your cell with the data from object
return cell;
}
You can normalize the time component of your attribute in the awakeFromFetchmethod of your NSManagedObject.
- (void) awakeFromFetch {
[super awakeFromFetch];
NSDateComponents* comps = [[NSCalendar currentCalendar] components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:self.itemDate];
self.itemDate = [[NSCalendar currentCalendar] dateFromComponents:comps];
}
In addition, looking at your code, you have to tell the NSFetchedResultsController on what it should base the section breaks. You do this by providing a keyPath for the section breaks when you initialize it. For your case, instead of using sectionNameKeyPath:#"sectionIdentifier", use the first sort key:
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"itemDate" cacheName:nil];

NSFetchedResultsController - different sort order than section name

I have an NSManagedObject for the sections in the grouped UITableView.
This object has the attributes "name" and "createdAt".
I want to use "name" in te UI for the section titles, but sorted by "createdAt".
According to the documentation the first sortDescriptor key has to be also the sectionNameKeyPath of the NSFetchedResultsController.
I suggested using two sortDescriptors, but it doesn't work. The sections are still sorted by name.
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:[CoreDataHelper instance].managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSSortDescriptor *sortDate = [[NSSortDescriptor alloc] initWithKey:#"createdAt" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortName, sortDate, nil]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[CoreDataHelper instance].managedObjectContext sectionNameKeyPath:#"name"
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
return _fetchedResultsController;
}
A fetched results controller (FRC) uses only the first sort descriptor to group (and sort) the objects into sections. A second sort descriptor can be added to sort the objects within each section.
Also, the key path of the sort descriptor must be the same as the sectionNameKeyPath of the FRC (or at least generate the same relative ordering).
See also Creating a Fetched Results Controller in the “Core Data Programming Guide”:
... In this example you add one more NSSortDescriptor instance to the
NSFetchRequest instance. You set the same key from that new sort
descriptor as the sectionNameKeyPath on the initialization of the
NSFetchedResultsController. The fetched results controller uses this
initial sort controller to break apart the data into multiple sections
and therefore requires that the keys match.
In your case, you can proceed as follows:
Use createdAt as sectionNameKeyPath and in the first sort descriptor.
Modify the titleForHeaderInSection delegate function to return the name property instead of createdAt:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
return [[[sectionInfo objects] objectAtIndex:0] name];
}
Note: If you have multiple objects with the same name but different createAt values, these will be grouped into different sections with the above approach. I don't know if that is a problem for you.
You're almost there. You need to define the sectionNameKeypath in your initWithFetchRequest call.
NSSortDescriptor *sortDate = [[NSSortDescriptor alloc] initWithKey:#"createdAt" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDate]];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[CoreDataHelper instance] sectionNameKeyPath:#"name"];

Core Data attribute count and show the result in a TableView

I've got an entity called Car with 2 attributes: name and color.
I have several cars in the database:
name: Honda Civic
color: Blue
name: VW Golf
color: Blue
name: Renault Twingo
color: Red
name: Chevrolet Camaro
color: White
Now I need to count how many cars are there of each color and then put the results in a UITableView.
So, for example, in the TableView would have:
Blue (2)
Red (1)
White (1)
Imagine that there are about 70 different colors.
I searched many websites and books of Core Data, but still I do not know if this is possible.
Is it possible? If possible, how to do it?
These are simple fetches. In fact, you can use a NSFetchedResultsController, and set "color" as the section name, and it will return to you an array of all objects, and they will be grouped by color.
Something like...
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Car"];
NSSortDescriptor *sectionSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"color" ascending:YES];
NSSortDescriptor *nameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sectionSortDescriptor, nameSortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"color" cacheName:#"CarCache"];
fetchedResultsController.delegate = self;
self.fetchedResultsController = fetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Handle error...
}
Now, your data will be broken up into sections, one section per color, and the values in there sorted by name. To get the data, just look at the sections property of the FRC.
I think the previous answer is not quite right. In your fetched results controller, you do the usual things but
You fetch the entity Color
You sort by name (that would be the name of the color)
In cellForRowAtIndexPath: you construct the appropriate string to display.
Color *color = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:#"%# (%d)",
color.name, color.cars.count];
where cars is the name of the inverse relationship from color to the cars.

Resources