I am sorting and sectioning a tableview using the first letter of an array of strings, just like the Contacts app does. I'm using Core Data and NSFetchedResultsController to drive the tableview. I use the first letter of the word, and if the letter isn't Only issue is, when I use a sort description with localizedCaseInsensitiveCompare:, it seems to generate the list of sections with: '#', 'A', 'B' ... 'Z'. I want the # to come at the end of the list, not the first (just like the Contacts app, again). Is there any creative way I could accomplish this?
Here's how I'm creating the NSFetchedResultsController:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:100];
NSSortDescriptor *sortDescriptorLetter = [[NSSortDescriptor alloc] initWithKey:#"sectionLetter" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorLetter, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"sectionLetter" cacheName: nil]; // NOTE: set the cache name to nil for testing ...
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
...
Rather than using the ‑localizedCaseInsensitiveCompare: selector to compare the objects, you should instead initialise your NSSortDescriptor using the +sortDescriptorWithKey:ascending:comparator: class method.
This method allows you to pass in an NSComparator which is a block that contains code to compare two objects and sort them. You can compare them any way you like.
If you've never used blocks before, this will be helpful.
Apparently you can't customize sort descriptors when using the NSFetchedResultsController. You have to go with the standard sorting selectors, like "localizedCaseInsensitiveCompare".
The way I ended up doing this is to take the NSFetchedResultsController results and tweaking the boilerplate code for setting up the tableView with my own mapping of indexPaths, where I off-set the 0th section to the last section of the table. It's a bit of work, and it might just be better to forget using NSFetchedResultsController and load all the objects directly.
Related
I have an CoreData entity called "MyPhoto" with the attributes :- photoData, timeStamp, folderName. Setting the today Date with Time to timeStamp property when saving photoData to core data. I want sections with folderName and inside the section I want to sort the photos with timeStamp. I am using and NSFetchResultsController to fetch the data & UICollectionView to display it.
Problem is: When I try to insert the new photo, it is not inserting in the correct sorting order but when I relaunch the app, it will show the all the photos in a correct sorting order.
here is the code I am using:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *moc = [[CoreDataManager sharedInstance] managedObjectContext];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyPhoto" inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Sort using the timeStamp property.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"folderName" ascending:YES];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
[fetchRequest setSortDescriptors:#[sortDescriptor, sortDescriptor1]];
// Use the folderName property to group into sections.
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc sectionNameKeyPath:#"folderName" cacheName:#"Root"];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
I hope I have explained my problem clearly. Thanks in advance for your help.
I see you are setting the delegate. Have you actually implemented any of the delegate methods?
For starters, you could just call reloadData in controllerDidChangeContent:
I currently have a class which has a date object in it. This date object has both time and day in it. All this information gets loaded into a UITableViewCell via a NSFetchedResultsController. I need to sort the dates into sections where each section is the date without the Time. I also need each section to be sorted in itself by time. Here is what my current _fetchedResultsController looks like:
[self.managedObjectContext lock];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Entity" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"due" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
theFetchedResultsController.delegate = sender;
[self.managedObjectContext unlock];
return theFetchedResultsController;
The attribute I need to sort is the Entity.due attribute.
You may want to check out the Core Data Sample called "DateSectionTitles" for more insight, I'm highlighting the basics from it.
To have sections according to date, you need to first sort by a normalized (or simplified) date. Try the following:
Add a transient attribute to your entity in the core data model editor. Ensure that it's type is NSDate and it's transient property is checked. Update your NSManagedObject class files by adding the property as a strong NSDate. Call it, "simplifiedDate". Also add another property to your .h file, call it "primitiveSimplifiedDate". Make it a strong.
In the .m use #dynamic for both the simplifiedDate and primitiveSimplifiedDate. This transient attribute should use return an NSDate that has been normalized to midnight. This level sets everything and allows you to establish the sections. I've used code along the lines of this:
-(NSDate *)simplifiedDate{
// Create and cache the section identifier on demand.
[self willAccessValueForKey:#"simplifiedDate"];
NSDate *tmp = [self primitiveSimplifiedDate];
[self didAccessValueForKey:#"simplifiedDate"];
if (!tmp) {
tmp=[self simplifiedDateForDate: [self due]];
[self setPrimitiveSimplifiedDate: tmp];
}
return tmp;
}
-(NSDate *)simplifiedDateForDate:(NSDate *)date {
if (!date) {
return nil;
}
static NSCalendar *gregorian = nil;
gregorian=[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *newDateComponents=[gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit| NSDayCalendarUnit) fromDate:date];
return [gregorian dateFromComponents:newDateComponents];
}
If you so desired, this could also be an actual attribute that is calculated and stored when the actual date is set.
Next, you will have two sort descriptors. The first sort descriptor will be the above transient attribute, "simplifiedDate". The second attribute will be the "due" attribute. The "simplifiedDate", so long as the NSFetchedResultsController is setup correctly in the next step, will sort everything according to the section.
Finally you need to provide the section key name when you alloc/init the NSFetchedResultsController. In this case it will be "simplifiedDate". Like this:
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"simplifiedDate"cacheName:#"Root"];
Hope this helps.
t
I am making a view just like the iPhone Calendar ListView. I am using core data and getting appointments and grouping them by date.
However just like in the iPhone listview I need to add a blank section for today even if there are no appointments. I cannot figure out how to do this for a section with no appointments since I am doing the sorting before I create the grouping.
How would I add an empty section to the NSFetchedResultsController and have it resorted so that today's date is in the correct spot and not at the end of the list?
- (NSFetchedResultsController *)fetchedResultsController {
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Appointments" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
[fetchRequest setEntity:entity];
//[fetchRequest setIncludesPendingChanges:YES];
// Set the batch size to a suitable number.
//[fetchRequest setFetchBatchSize:20];
// Sort using the date / then time property.
NSSortDescriptor *sortDescriptorDate = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
NSSortDescriptor *sortDescriptorTime = [[NSSortDescriptor alloc] initWithKey:#"start_time" ascending:YES selector:#selector(localizedStandardCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorDate, sortDescriptorTime, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Use the sectionIdentifier property to group into sections.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext] sectionNameKeyPath:#"date" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
return fetchedResultsController;
}
You can't have empty sections with NSFetchedResultsController - that's just the way it is designed at the moment, and I would call it a limitation :)
This problem has been encountered and addressed by a Timothy Armes who has created a class TAFetchedResultsController, which allows for empty sections. It's a replacement for NSFetchedResultsController. It also allows you to sort your sections on fields which aren't the section name (quite handy)
However, you will need to make changes to your Core Data model - it's not quite a drop in replacement.
https://github.com/timothyarmes/TAFetchedResultsController
But it does work well, and will solve your problem if you are willing to re-do your data model.
Hope this helps :)
I am using CoreData and executing the following command returns a number of objects:
[Contact findAll];
However when I setup a simple NSFetchrequest like so, I get 0 objects.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Contact" inManagedObjectContext:self.context];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.context
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
NSArray *array = [self.context executeFetchRequest:fetchRequest error:&error];
Please tell me what I am doing wrong here. Since I don't have a Predicate set, I would expect to get the same number of objects, but I get an array with 0 objects and error = nil.
Please note that self.context is not nil.
If you're using RESTKit, I'm guessing that RESTKit is setting up its own Core Data context/store and when you're doing it manually, you're using the default ones provided by Apple's Xcode template. Stop using self.context and get the context from RESTKit, or use one of RESTKit's convenience methods such as objectsWithFetchRequest:.
RestKit as of 0.10 maintains a per-thread object store. This can be tricky if RestKit hasn't finished with your data (ie: your RKRequestQueue has finished but it's still processing your mappings).
You can access a foreign store with the Active Record categories, ie:
NSManagedObjectContext *restContext = [[[RKObjectManager sharedManager] objectStore] managedObjectContextForCurrentThread];
NSArray *allTexts = [TextEntity findAllInContext:restContext];
Hope this helps!
I am using a background thread to fetch limited number of records sorted with date.
Everything works well until I delete a record in the UI thread (tableview ).
//this is done in the background thread
NSFetchRequest *frequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity"
inManagedObjectContext:self.managedObjectContext];
[frequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"date"
ascending:NO];
NSArray *descriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[frequest setSortDescriptors:descriptors];
[frequest setFetchOffset:fetchOffset];
[frequest setFetchLimit:20];
[frequest setResultType:NSManagedObjectIDResultType];
NSError *fetchError;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:frequest
error:&fetchError] mutableCopy];
The background thread is registered for NSManagedObjectContextDidSaveNotification and performs the following selector
//this is done in the background thread
-(void) didSavePersistenceStore:(NSNotification *)notification
{
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
The problem: After deleting a record, subsequent fetch results are not sorted with date anymore.
What am I missing ?
First, make sure you do not use the managedObjectContext from the wrong thread. You can call performBlock to do it in the proper context.
Second, the fetch descriptor you use for the fetch does not persist. So, unless you keep fetching with the same sorting criteria, you won't get it that way.
If you want that behavior, use a fetch results controller. It will maintain your desired view into the database.
From Apple documentation:
If you set the value to NSManagedObjectIDResultType, this will demote any sort orderings to “best efforts” hints if you do not include the property values in the request.
You need to write all changes to persistent store or set includesPendingChanges to NO.