NSFetchedResultsController - only show results saved in database? - ios

I'm using Core Data and an NSManagedObject as the object model for temporarily storing the data in the app before it's saved.
Then in a tableview I display all saved instances. However, the temporary object is also appearing in the list. How can I ensure my tableview only displays results that are actually saved in the database?
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"State" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[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:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}

You can set
[fetchRequest setIncludesPendingChanges:NO];
From the documentation:
The default value is YES.
If the value is NO, the fetch request skips checking unsaved changes
and only returns objects that matched the predicate in the persistent
store.

NSManagedObjectContext NSFetchedResultsController will automatically save the context. So your change is already saved to database before you restore data in tableView
Core Data can manage data in-memory, which is describe as :
Core Data provides an infrastructure for change management and for
saving objects to and retrieving them from storage. It can use SQLite
as one of its persistent store types. It is not, though, in and of
itself a database. (To emphasize this point: you could for example use
just an in-memory store in your application. You could use Core Data
for change tracking and management, but never actually save any data
in a file.)
to solve your problem, you can create another NSManagedObjectContext, which use to save your temporary data. when you want to save these temp data to your database, just create new NSManagedObject and copy the data info from de temp data and save it to your permanent database context

Related

Why do I have to comment out my NSPredicate to have NSFetchedResultsController populate an UITableView?

I have a strange bug: if I uncomment my NSPredicate, the resulting UITableView is empty.
My data Model is the following:
Category <-->> Feed <-->> Post
I am fetching the Posts. Post.feed is a Post's Feed. Feed has an rss NString property.
Here's the code:
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (_fetchedResultsController == nil) {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Post"
inManagedObjectContext:_globalMOC];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"feed.rss == %#", _detailItem.rss];
[fetchRequest setPredicate:predicate];
// 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:_globalMOC
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate.
// You should not use this function in a shipping application, although it may be useful
// during development. If it is not possible to recover from the error, display an alert
// panel that instructs the user to quit the application by pressing the Home button.
//
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
return _fetchedResultsController;
}
As I told before, I only see results if I uncomment the NSPredicate. I tried with LIKE, ==, =, with double and single quotes around %#...
BTW, The best would be to directly compare the Feed object...
According to Apple, the syntax might not be the issue, but then what?
The Posts are created in a separate ManagedObjectController sharing the same PersistentStoreCoordinator. I get the required Feed's objectID in order to associate the new Post with its corresponding Feed in the child MOC (otherwise I'd get an error regarding associating objects from different MOC).
I also duely merge my MOCs in the main thread whenever the child MOC notifies it of a change.
Basically: if I NSLog the Posts I have (commented-NSPredicate), I see every Post with the relevant RSS Feed URL fitting the displayed Feed (= detailItem).
Anyone can help me?
If your NSFetchedResultsController is blank then it's pretty sure that you're getting no results through the fetch request and that i'm afraid, because of inappropriate predicate statement or no matching records. i guess the problem is due to presence of wildcard(don't know much about that)
check NSPredicate Class Reference and Predicate Programming Guide to get accurate results through predicates.
Eureka!
The problem was the following: when I create my NSFetchedResultsController in my DetailView, _detailItem is nil.
So, even after when setting _detailItem, the NSPredicate still focus on comparing my feed relationship to a nil object.
I solved the problem by refreshing my NSFetchedResultsController.fetchRequest in the didSelectRowAtIndexPath in the MasterView the following way:
Feed *feed;
if (tableView == self.searchDisplayController.searchResultsTableView) {
feed = [_filteredCategoryArray objectAtIndex:indexPath.row];
} else {
feed = [[self fetchedResultsController] objectAtIndexPath:indexPath];
}
self.detailViewController.detailItem = feed;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"feed == %#", feed];
[self.detailViewController.fetchedResultsController.fetchRequest setPredicate:predicate];
Hope this solution might help other people.
Thanks for your help, nickAtStack!

Updating an NSFetchedResultsController with an NSPredicate that uses the current time

I have an application that needs to filter objects based on timestamps. For example, lets say I want to filter an Event to only display Events that are in the past. I want to then display them in a UITableView. I would set up an NSFetchedResultsController like so:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
// Filter based on only time stamps in the past
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"timeStamp < %#", [NSDate date]];
fetchRequest.predicate = predicate;
[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:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
My question is this: what is the best way to update this view so that the filter is based on the current time? My existing solution is to set up a method like this:
- (void)updateFetchedResultsController {
self.fetchedResultsController = nil;
[self.tableView reloadData];
}
Then I call that method on viewWillAppear: or viewDidAppear:. This works unless the user stays on the screen for a while.
I could also use an NSTimer and call updateFetchedResultsController once a minute or so but that causes issues if the user is scrolling through the table. Is there a better way to check if the data has changed? Since the data isn't changing I can't rely on any save events.
You only ever need to change the data on display when an items time is no longer valid. It has a date so you can calculate how long into the future that is and set a timer. You order the data so the next item to expire is always the first in the list.
To finesse, you can check for scrolling when the timer expires and delay the reload until the scroll animations have completed.

Exception on Core Data save

This seems a fairly common workflow I have an issue with that I cannot figure why. I have a UITableView with NSFetchedResultsController supplying the rows. To add a new object, I insert into context and then present a new view controller to edit the details. The add button action is like so:
-(IBAction)addNewIssueType:(id)sender
{
IssueType *newUntitledType = [NSEntityDescription insertNewObjectForEntityForName:#"IssueType" inManagedObjectContext:self.managedObjectContext];
newUntitledType.name = NSLocalizedString(#"Untitled Task", #"Name for newly created task");
[self saveContext];
self.editingType = newUntitledType;
[self presentNameEditViewForType:newUntitledType];
}
I am crashing with exception at the saveContext method, error:
2013-05-11 16:25:35.990 App [18843:c07] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. The left hand side for an ALL or ANY operator must be either an NSArray or an NSSet. with userInfo (null)
2013-05-11 16:25:35.992 App [18843:c07] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'The left hand side for an ALL or ANY operator must be either an NSArray or an NSSet.'
* First throw call stack:
Taking the notification observer suggestion as a clue, I looked at my NSFetchedResultsController, presumably the only observer of the NSManagedObjectContext in scope:
-(NSFetchedResultsController *)typesController
{
if (_typesController) {
return _typesController;
}
// 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:#"IssueType" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Filter out the required types
NSPredicate *exclude = [NSPredicate predicateWithFormat:#"NONE name in %#",excludedTypes];
[fetchRequest setPredicate:exclude];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects: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:nil];
aFetchedResultsController.delegate = self;
self.typesController = aFetchedResultsController;
NSError *error = nil;
if (![_typesController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
[self failAlertWithMessage:nil forceAbort:YES];
}
return _typesController;
}
I found that, if I comment out the lines creating and assigning the predicate, it does not crash. Of course then I am showing some objects that I want hidden. I can't see anything wrong with the predicate itself, fetch does return the expected objects and excludes those in the excludedTypes Array as intended.
Save context method does not have any issues on editing or deleting existing objects, only on inserting a new object.
I am not using and threads here other than the main thread.
NONE is an alias for NOT ANY and is indeed for to-many relationships.
What you probably want is:
[NSPredicate predicateWithFormat:#"NOT name in %#", excludedTypes];

Core Data: Folder like functionality

Im trying to make a entity in core data that acts like a folder, like on a mac basically where you can put a object inside, and another folder. currently i have folders, and the individual bookmark (another entity) and bookmarks can be in the folder, but i cant get Folders to go inside another folder.
Here is my NSFetchedResultsController,
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Bookmark" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
[NSFetchedResultsController deleteCacheWithName:#"Folder"];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"folder == %#", self.folder];
[fetchRequest setPredicate:pred];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[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.context sectionNameKeyPath:nil cacheName:#"Folder"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
if the NSEntityDescription Entity was NSEntityDescription *entity = [NSEntityDescription entityForName:#"Folder" inManagedObjectContext:self.context]; it would crash with the error,
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath folder not found in entity '.
How can i fix this? i am stumped at this issue.
The Folder entity has a to-many relationship with the bookmark entity, the bookmark entity has the following attributes, title, url. Folder has the following attributes, title, displayOrder, isFolder.
NSPredicate *pred = [NSPredicate predicateWithFormat:#"folder == %#", self.folder];
This line is fine when your entity is a bookmark. If you just change the entity to Folder, and leave this predicate in place, then the fetch request will look for the key folder on a Folder entity, which doesn't exist according to your data model description above. If you want to fetch all folders, don't set a predicate.
To allow folders to contain folders, you need to define a relationship from the Folder entity to itself, something like this:
Here the Folder entity has two relationships, a to-many relationship called subfolders, and its inverse, a to-one relationship called parentFolder. The top level folder has nil for its parentFolder.
When fetching the folders contained in a folder, you either simply use the set returned by folder.subfolders, or a fetch request predicate where "parentFolder == %#",folder.

tableview reloadData doesnt work - TableView Data are not correctly sorted

i have an app with an On and Off Mode.
In Online Mode, i am checking a login to a server,
downloading an XML-File and parsing that File. All Datas are written to coreData.
I am populating my TableView with NSFetchedResultsController.
If i use the Off Mode (To use the Offline mode, the coreData Entitys should not be nil),
i just dismiss the view to display the tableView with the data from coreData.
Problem here is: The data is not sorted correctly if i use the Offline Mode.
reloadData doesn't work, i have tried it more Times.
How can i relaod the tableView, so i get the data correctly sorted in off mode, too??
Edit 1:
In Off mode i only do this:
if (xmlFile) {
//FadeOut animation nach erfolgreichem Login
//und removeFromSuperview
[UIView animateWithDuration:0.8
animations:^{loginView.alpha = 0.0;}
completion:^(BOOL finished){ [loginView removeFromSuperview]; }];
}
How can i do sort the tableview on viewDidAppear?
Edit 2:
Here my NSFetchedResultsController:
#pragma mark - Fetched results controller
- (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] autorelease];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntitySetsCards" inManagedObjectContext:self.managedObjectContext];
NSPredicate *inboxPred = [NSPredicate predicateWithFormat:#"archived == 0"];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
[fetchRequest setPredicate:inboxPred];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptorSetOrder = [[[NSSortDescriptor alloc] initWithKey:#"sortOrder" ascending:YES] autorelease];
NSSortDescriptor *sortDescriptorColorOrder = [[[NSSortDescriptor alloc] initWithKey:#"colorOrder" ascending:YES] autorelease];
NSSortDescriptor *sortDescriptorSortOrder = [[[NSSortDescriptor alloc] initWithKey:#"sortingOrder" ascending:YES] autorelease];
NSArray *sortDescriptors = [[[NSArray alloc] initWithObjects:sortDescriptorSetOrder, sortDescriptorSortOrder, sortDescriptorColorOrder, nil] autorelease];
[fetchRequest setSortDescriptors:sortDescriptors];
// nil for section name key path means "no sections".
// cacheName auf nil gesetzt, da #"Root" fehler erzeugt hat (FATAL ERROR)
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"setTitle" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
allFetchedCards = aFetchedResultsController;
allCards = [__fetchedResultsController fetchedObjects];
return __fetchedResultsController;
}
Data stored in the database is not sorted by default. It is your responsibility to sort it. Depending on your data structure you can either add an index to aid in sorting or you can sort by some logical value such as name, lastSeenDate, etc.
Post the code that is retrieving the data from Core Data (probably the creation of your NSFetchedResultsController).
Response to OP 1
You really don't want to sort outside of the NSFetchedResultsController as that involves a lot of effort and code that is really unnecessary. You should be configuring the NSFetchedResultsController to sort in the way that you are going to present it on screen. There really is no reason to do it twice. Sorting is non-destructive and non-persistent.
However if you absolutely must sort twice then you would need to:
Gather the array of objects either via -fetchedObjects or via -sections then -objectAtIndex: and then -objects to obtain the array for a section.
With the array in hand you can call -sortedArrayUsingDescriptors: to produce the final sort.
As I said, you really don't want to do this. You would need to do this at every point that you interacted with the NSFetchedResultsController in your code which in a typical UITableViewController is about 5 points. Lots of extra code for zero value.
An additional question for you: Are you using the NSFetchedResultsController to display the data for the cells? Can you post your -tableView: cellForRowAtIndexPath:?

Resources