NSFetchedResultsController with sections sorted by TWO criteria - ios

I have a table view that displays a musician's albums. Each section is an Album, each row is a Track. I need the albums/sections sorted by release date, then by title. I'm pulling the tracks from Core Data like so:
fetchRequest = [[NSFetchRequest alloc] init];
...
fetchRequest.sortDescriptors = [NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"album.releaseDate" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"album.title" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"trackNumber" ascending:YES],
nil];
frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:#"album.releaseDate" // <-- PROBLEM HERE
cacheName:nil];
This doesn't work because two albums with the same release date will appear in the same section.
If I use album.title as the sectionNameKeyPath, hell breaks loose because the sections are sorted alphabetically, then imposed on the tracks (which are sorted by date, title, trackNumber).
How do I sort the sections by date, then by title?

From what I've read, this single-property sorting is just something we have to live with on iOS. So I added a transient property to Album that acts as a sort key for both properties:
- (NSString *)sortKeyDateTitle
{
[self willAccessValueForKey:#"sortKeyDateTitle"];
NSString *sortKey = [NSString stringWithFormat:#"%#%#", self.releaseDate, self.title];
[self didAccessValueForKey:#"sortKeyDateTitle"];
return sortKey;
}
It produces strings like this:
1954-04-01 06:00:00 +0000A Night At Birdland, Vol. 1
1954-04-01 06:00:00 +0000A Night At Birdland, Vol. 2
It works, but converting from date to string for sorting seems stupid. I'm waiting for something better.

Related

Sorting rows in entity based on attribute in core data

I am having problem with my core data's entity. It has three attributes similar to this table
Name value details
cat xxx 01
apple YYY 10
ball ZZZ 11
I need to sort the table based on attribute "Name" to make the entity look like this
Name value details
apple YYY 10
ball ZZZ 11
cat xxx 01
Using the below code only sorts the "name" attribute values, but i need the entire row to be sorted.
NSFetchRequest *fetchRequest1 = [NSFetchRequest fetchRequestWithEntityName:#"UserData"];
fetchRequest1.resultType = NSDictionaryResultType;
[fetchRequest1 setPropertiesToFetch:[NSArray arrayWithObjects:#"userName", nil]];
fetchRequest1.returnsDistinctResults = YES;
NSArray *dictionaries1 = [self.managedObjectContext executeFetchRequest:fetchRequest1 error:nil];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"userName" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray* sortedArray=[dictionaries1 sortedArrayUsingDescriptors:#[sort]];
Is there a way to sort the entire row and save the entity ??
You have specified NSDictionaryResultType, so the fetch returns only the one attribute that you specify - username. To fetch the complete object, with all its attributes, delete those lines. You also don't need returnsDistinctResults. And you might as well get the fetch to do the sorting, rather than sorting the array afterwards:
NSFetchRequest *fetchRequest1 = [NSFetchRequest fetchRequestWithEntityName:#"UserData"];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"userName" ascending:YES selector:#selector(caseInsensitiveCompare:)];
fetchRequest1.sortDescriptors = #[sort];
NSArray *sortedResults = [self.managedObjectContext executeFetchRequest:fetchRequest1 error:nil];

CoreData NSFetchedResultsController Sorting

I have a small problem with sorting my NSFetchedResultsController.
My NSManagedObject has two attributes. date and startTime.
The date is of time 00:00:00 on all my objects, this way when using date as the sectionNameKeyPath it grabs all the objects with identical dates (by day) into one section. If the time of the dates were different, it would put every object into a different section.
This works well, but then inside each group I want to sort the objects by startTime. So they are list from earliest on that date to the latest in each section respectively.
My issue is when using date as the sectionNameKeyPath and startTime as an NSSortDescriptor` it doesn't like it and plays weirdly. Such as only sometimes showing certain data in what seems like an irregular way.
I think it comes down to having to have the sort descriptor and sectionNameKeyPath the same. Am I right in thinking this? If not, how should I setup my NSFetchedResultsController to list my data in the fashion mentioned?
Thanks.
EDIT: Here is come code... Also worth noting when using startTime as my second sort descriptor, it causes duplicates to display in my tableview with nil objects.
NSFetchedResultsController:
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"startTime" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor1, sortDescriptor2];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"date" cacheName:#"Master"];
cellForRowAtIndexPath just a snippet showing how I designate each managed object:
id <NSFetchedResultsSectionInfo> sectionInfo = [self.flightFetchedResultsController.sections objectAtIndex:indexPath.section];
NSArray *sectionFlights = [sectionInfo objects];
Flight *flight = [sectionFlights objectAtIndex:indexPath.row];
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.flightFetchedResultsController.sections.count;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.flightFetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
Your section key name path needs to match the first sort descriptor.
So you could do...
// sectionKeyNamePath = #"date".
NSSortDescriptor *dateSD = [NSSortDescriptor sortDescriptorWithKey:#"date" ascending:YES];
NSSortDescriptor *startTimeSD = [NSSortDescriptor sortDescriptorWithKey:#"startTime" ascending:YES];
frc.request.sortDescriptors = #[dateSD, startTimeSD];
If you do this then it will sort (and section) by date and then sort each section by startTime.
From your code
You are getting the fetched objects incorrectly.
To get an object you need to use...
Flight *flight = [self.frc objectAtIndexPath:indexPath];
A fetched results controller knows about its sections and rows. You don't need to split it apart.

iOS: Sorting and grouping NSFetchedResultsController

I have two tables with the following structure
MainCategory:
name position hasSubCategories
SubCategory:
name position display belongsToMainCategory
Now I want to display all subcategories (where display attribute = YES) grouped by main category. The main category sections should be sorted as defined by position and the subcategories itself (within the section) by their position attribute. (name by the way can be the same for a certain main category...my tables have more attributes but they aren't relevant to understand the problem).
But my order is completely messed up. Why? Here's my code for the FetchedResultsController:
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"SubCategory"];
NSSortDescriptor *mainCatPosition = [[NSSortDescriptor alloc]
initWithKey:#"belongsToMainCategory.position" ascending:YES];
NSSortDescriptor *subCatPosition = [[NSSortDescriptor alloc]
initWithKey:#"position" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObjects:mainCatPosition,subCatPosition,nil];
request.predicate = [NSPredicate predicateWithFormat:#"display = %#", [NSNumber numberWithBool:YES]];
[self.db.managedObjectContext executeFetchRequest:request error:&error];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request
managedObjectContext:self.budgetDatabase.managedObjectContext
sectionNameKeyPath:#"belongsToMainCategory.name"
cacheName:nil];
The key path used as sectionNameKeyPath: argument to the fetched results controller must
be the same key that is used in the first sort descriptor or generate the same relative ordering.
The fetched results controller first orders all fetched objects according to the first
sort descriptor and then groups the objects into sections according to the sectionNameKeyPath. Therefore using different key paths (as in your case "belongsToMainCategory.position" vs. "belongsToMainCategory.name") does not work.
This could even cause a runtime error about "out of order sections".

NSFetchedResultContoller can do that?

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.)

Core Data sort tableview by formatted date

I know how to sort Core Data objects in a tableview by NsDate, but this by default seems to create a new section for each object. I want to sort them by a medium formatted date with NSDateFormatter. How would I do this?
For example, if I have 3 objects created on the same day, I want them to be in the same section with the section title being that Day, no time needed.
Each object has an NSDate property. Thanks for your help.
This is the code I have in fetchedResultsController with rgeorge's suggestions. What am I missing here?
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
NSLog(#"get old fetched controller");
return fetchedResultsController;
}
else{
NSLog(#"get new fetched controller");
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InTextEntity" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *dateDescriptor = [[NSSortDescriptor alloc] initWithKey:#"dateModified" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:dateDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"mediumFormattedDate" cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
return fetchedResultsController;
}
(I'll write this up assuming you're using an NSFetchedResultsController to drive your tableview. If you're not, I recommend checking it out.)
An interesting feature of NSFetchedResultsController's sectioning abilities: although the property you sort on must be a modeled property (because sqlite does the actual sorting), the property you group the sections with need not be. The only requirement is that the grouping be consistent with the ordering. (i.e., sorting by the sort property will put the objects with matching group properties next to each other.)
So just add something like this to your modeled object class:
// in interface
#property (nonatomic, readonly) NSString *mediumFormattedDate;
// in impl
-(NSString *)mediumFormattedDate
{
// this can be fancier if you need a custom format or particular timezone:
return [NSDateFormatter localizedStringFromDate:self.date
dateStyle:NSDateFormatterMediumStyle
timeStyle:NSDateFormatterNoStyle];
}
(no need to mention mediumFormattedDate in the .xcdatamodel at all.)
Then go ahead and sort your objects by the date property, but group them by your new property. When you create your NSFetchedResultsController, do so along these lines:
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"MyFancyEntity"];
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:#"date"
ascending:YES];
[fr setSortDescriptors:[NSArray arrayWithObject:sd]];
NSFetchedResultsController *frc =
[[NSFetchedResultsController alloc] initWithFetchRequest:fr
managedObjectContext:myManagedObjectContext
sectionNameKeyPath:#"mediumFormattedDate"
cacheName:nil];
// then do stuff with frc
That's all it takes! I've done this in a few apps to get date grouping and it works well.
Sounds like you're setting the section index on the fetched results controller to be your date property, which seems undesirable.
Instead you should probably be computing the section index yourself, and sorting by date. You can accomplish this in either your data model or by computing the sections manually in code.
For example, you could add a property to your managed object model called "Day" and set that to whatever value you want to use (you don't specify if its something like Monday or an actual date like 21).
You can then pass that property to the fetched results controller.
Alternatively you could implement the sections yourself, days are easy, its Monday-Sunday. Dates are a bit harder, 1-28,30,31 depending on what month it is. Then use an appropriate NSPredicate / NSFetchRequest to get the count of the items in each section.

Resources