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 :)
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 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.
I got 5 fetched results controllers now, and am adding my first one to actually work with data, rather than just displaying it. I expect the controller to manage up to 150 objects. What batch size should I choose to work with up to 5 objects a time, in sequence? Is it 5?
- (NSFetchedResultsController *)estimatorEventsController
{
if (__estimatorEventsController != nil)
{
return __estimatorEventsController;
}
/*
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:#"EstimatorEvent" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:36];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"epoch" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[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:nil cacheName:#"EstimatorEvents"];
aFetchedResultsController.delegate = self;
self.estimatorEventsController = aFetchedResultsController;
NSError *error = nil;
if (![self.estimatorEventsController performFetch:&error])
{
NSLog(#"Unresolved error __remindersController %#, %#", error, [error userInfo]);
// abort();
}
return __estimatorEventsController;
}
I appreciate your help!
Alex,
I have 800+ items in my fetched results controllers and don't batch the fetches. Don't worry about 150 items ... you know, premature optimization and all that.
The other thing to remember is the the batch limit is on the fetch request not the fetched results controller. This distinction is important because the fetch request is a lower level item that can easily trigger a results array containing thousands of items. One must be careful with queries of such sizes. 150 rows is just not a big deal.
Andrew
I don't have a terribly informed answer, but it seems that most guides appear to be proposing that you load about two to three times as many cells as will be appearing on the screen at any one time, so if you have five visible in the table at a time, then perhaps 15? It is a balance between the benefits of not loading everything at once, versus not having too many fetch requests as they have some load and latency and making sure you already have some cells that are not yet on screen pre-fetched to be responsive during sudden rapid scrolling.
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.