NSFetchResultController: Only Initial Load shows no data in the tableViewController's table - nsfetchedresultscontroller

I have:
#interface CMainTableViewController : UITableViewController
In that class I have an NSFetchResultController.
I have the following separate data manager class:
DB_ManagerSingleton
//Here I have a UIManagedDocument that creates the DB/persistent store
To open my persistent store I do the following from my DB_ManagerSingleton class:
UIManagedDocument *fileDB = [[UIManagedDocument alloc]initWithFileURL:url];
if([[NSFileManager defaultManager] fileExistsAtPath:self.fileDB.fileURL.path] != TRUE)
{
[self.fileDB saveToURL:self.fileDB.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success)
{
[self completeDBSetup:success];
}];
}//End if
else if(self.fileDB.documentState == UIDocumentStateClosed)
{
[self.fileDB openWithCompletionHandler:^(BOOL success)
{
[self completeDBSetup:success];
}];
}//End else if
else if(self.fileDB.documentState == UIDocumentStateNormal)
{
[self completeDBSetup:TRUE];
}//End else if
The completeDBSetup places 4 NSManageObjects into the NSManagedObjectContext and notifies the registered observers of the success that the persistent store was opened:
if([CLevelFactory createLevels] == TRUE)
[self.fileDB.managedObjectContext save:&error];
for(id<DB_StateNotification> observer in self.arrNotificationDelegates)
{
if(observer != nil)
[observer DB_StateChange:self.isDBSetup];
}//End for loop
Now, in this case the observer is my TableViewController class. Within this class I have implemented all the methods in the NSFetchedResultsControllerDelegate and the implementation is copied exactly from the Coredata books example.
http://developer.apple.com/library/ios/#samplecode/CoreDataBooks/Listings/Classes_RootViewController_m.html#//apple_ref/doc/uid/DTS40008405-Classes_RootViewController_m-DontLinkElementID_14
The way I set up my tableView controller in the DB_StateChange method from above is as follows:
self.fetchResultController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:dbMgr.fileDB.managedObjectContext
sectionNameKeyPath:#"levelID"
cacheName:nil];
self.fetchResultController.delegate = self;
NSError *error = nil;
self.bQuerierYourData = [self.fetchResultController performFetch:&error];
NSArray *fetchedObjects = [self.fetchResultController fetchedObjects];
//NOTE: I ALWAYS have a count greater than 0. Even on the first run.
if(fetchedObjects.count > 0)
[self.tableView reloadData];
Now the issue is this:
The very first time my application launches I get squat in my table view. I get a fetchedObjects.count of 4 - which is what I expect, but not'a in the table view. Now, without touching anything. Not inserting, updating, or deleting anything in the persistent store, I just quick the application. In this case hitting the stop button on the debugger. When I go ahead and restart the app, everything comes in beautifully. I don't insert data every time the app starts, I test and if the data already exists, I just move on without inserting any data.
Does anyone have any idea what is going on here?

Related

Parse.com Query: Cache is always empty (iOS)

I'm writing an iOS-App using Parse.com as a backend.
In the - (PFQuery)queryForTable method of my PFQueryTableViewController I'm retrieving a set of data from Parse, but I'm unable to cache this query in order to support functionality when the device is currently offline.
The method looks as followed:
- (PFQuery *)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
[query whereKey:#"city" equalTo:[[NSUserDefaults standardUserDefaults] objectForKey:#"city"]];
// userMode is active when a user is logged in and is about to edit coins
if (self.userModeActive) {
[query whereKey:self.textKey equalTo:self.user[#"location"]];
}
// dateFilter is active when view is pushed from an event
if (self.dateFilterActive) {
[self createDateRangeForFilter];
[query whereKey:#"date" greaterThan:[[self createDateRangeForFilter] objectAtIndex:0]];
[query whereKey:#"date" lessThan:[[self createDateRangeForFilter] objectAtIndex:1]];
} else {
// Add a negative time interval to take care of coins when it's after midnight
[query whereKey:#"date" greaterThanOrEqualTo:[[NSDate date] dateByAddingTimeInterval:-(60 * 60 * 6)]];
[query orderByAscending:self.dateKey];
}
// locationFilter is active when view is pushed from a location profile
if (self.locationFilterActive) {
[query whereKey:#"location" equalTo:self.locationToFilter];
}
// If no objects are loaded in memory, look to the cache first to fill the table
// and then subsequently do a query against the network.
if (self.objects.count == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}
if ([query hasCachedResult]) {
NSLog(#"hasCache");
} else {
NSLog(#"chache empty");
}
return query;
}
[query hasCachedResults] always returns false in this case.
In another class, I'm doing the almost exactly same query (on a different Parse-Class) and it caches automatically. The only difference is, that this other query contains PFFiles.
This may be a dumb question but I'm stuck with it for days now.
Thanks for any help and let me know if I can give you more information.
The code guards the setting of cache policy with a conditional if (self.objects.count == 0). It would seem that you're using the cache when there are zero objects and not using it after the query has succeeded. Since the default is to not use the cache, the code is arranged to never use it.
Just remove the conditional, or use the cache conditionally upon [query hasCachedResult]
EDIT - It is still the case that the cache policy can/should be set unconditionally, but a query can have hasCachedResults only if its criteria don't change after the find (I don't see a place in the docs confirming this, but it stands to reason). To insure that a query can return cached results, leave its criteria unchanged after the find.
The [NSDate date] avoid the cache of PFQuery. Here is a work-around:
do not query NSDate at viewDidLoad
but do it in viewDidAppear
The code:
- (PFQuery *)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
// 1. load from cache only when viewDidLoad
// setup query WITHOUT NSDate "where" condition
if (self.shouldQueryToNetwork) {
// 2. update objects with date condition only when view appeared
[query whereKey:#"date" greaterThan:[NSDate date]];
}
return query;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.shouldQueryToNetwork = YES;
// Sync objects with network
[self loadObjects];
}

NSTextField with NSFormatter results in broken continuous binding

I have a textfield which has to be unique so I added my custom NSFormatter (see below)
The formatter works, as you can see on the screenshot, but the continuous binding, which I am using is broken, so for example the bound text does no longer get updated in real-time.
I found a possible cause here, but I don't know how to work around this problem and re-enable the continuous binding:
...
12. If the view has an NSFormatter attached to it, the value is
formatted by the NSFormatter instance. Proceed to Step 17.
...
17. The updated value is displayed in the user interface.
So it looks like it's intentionally skipping the steps we want. This
is very annoying. I tried NSValueTransformer, but adding that to an
editable NSTextField makes it non-editable.
My formatter
- (BOOL)getObjectValue:(out id *)obj forString:(NSString *)string errorDescription:(out NSString **)error {
if([string isNotEqualTo:#"todo-invalid-value"]){
*obj = string;
NSLog(#"YES");
return YES;
} else {
if(error){
*error = #"ERROR: not allowed";
}
return NO;
}
}
- (NSString *)stringForObjectValue:(id)obj {
return (NSString *)obj;
}
Working validation
Please note that the title of the list item should be updated with the text, that I entered in the textfield.
I ran into the same problem over the weekend, and eventually discovered a post from 2008 by Yann Disser on the cocoa-dev mailing list which shed some light on my problem.
I had an existing NSFormatter that was working fine and when I broke down the components, so I spent a little more time on it this morning and located Yann's post.
The key is that you need to return a different object than the one that is passed in. It's subtle, but the docs say: If conversion is successful, upon return contains the object created from string.
The problem I was having stemmed from the fact that the NSString that was coming in was actually an NSMutableString and was getting modified later.
Here's the code modified to return [NSString stringWithString: string], which should fix your problem:
- (BOOL)getObjectValue:(out id *)obj forString:(NSString *)string errorDescription:(out NSString **)error {
if([string isNotEqualTo:#"todo-invalid-value"]){
*obj = [NSString stringWithString: string];
NSLog(#"YES");
return YES;
} else {
if(error){
*error = #"ERROR: not allowed";
}
return NO;
}
}

adding a Core Data object from a segue

in getting familiar with core data i have found myself puzzled by the question of what to pass various view controllers (VCs) when trying to add data.
for example, in the CoreDataRecipes project that apple provides as an example (http://developer.apple.com/library/ios/#samplecode/iPhoneCoreDataRecipes/Introduction/Intro.html) they use the following approach
when the user wants to add a recipe to the list of recipes presented in the master table view, and hits the Add button, the master table view controller (called RecipeListTableViewController) creates a new managed object (Recipe) as follows:
- (void)add:(id)sender {
// To add a new recipe, create a RecipeAddViewController. Present it as a modal view so that the user's focus is on the task of adding the recipe; wrap the controller in a navigation controller to provide a navigation bar for the Done and Save buttons (added by the RecipeAddViewController in its viewDidLoad method).
RecipeAddViewController *addController = [[RecipeAddViewController alloc] initWithNibName:#"RecipeAddView" bundle:nil];
addController.delegate = self;
Recipe *newRecipe = [NSEntityDescription insertNewObjectForEntityForName:#"Recipe" inManagedObjectContext:self.managedObjectContext];
addController.recipe = newRecipe;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:addController];
[self presentModalViewController:navigationController animated:YES];
[navigationController release];
[addController release];
}
this newly created object (a Recipe) is passed to the RecipeAddViewController. the RecipeAddViewController has two methods, save and cancel, as follows:
- (void)save {
recipe.name = nameTextField.text;
NSError *error = nil;
if (![recipe.managedObjectContext save:&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();
}
[self.delegate recipeAddViewController:self didAddRecipe:recipe];
}
- (void)cancel {
[recipe.managedObjectContext deleteObject:recipe];
NSError *error = nil;
if (![recipe.managedObjectContext save:&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();
}
[self.delegate recipeAddViewController:self didAddRecipe:nil];
}
i am puzzled about this design approach. why should the RecipeListViewController create the object before we know if the user wants to actually enter a new recipe name and save the new object? why not pass the managedObjectContext to the addRecipeController, and wait until the user hits save to create the object and populate its fields with data? this avoids having to delete the new object if there is no new recipe to add after all. or why not just pass a recipe name (a string) back and forth between the RecipeListViewController and the RecipeAddController?
i'm asking because i am struggling to understand when to pass strings between segues, when to pass objects, and when to pass managedObjectContexts...
any guidance much appreciated, incl. any links to a discussion of the design philosophies at issue.
Your problem is that NSManagedObjects can't live without a context. So if you don't add a Recipe to a context you have to save all attributes of that recipe in "regular" instance variables. And when the user taps save you create a Recipe out of these instance variables.
This is not a huge problem for an AddViewController, but what viewController do you want to use to edit a recipe? You can probably reuse your AddViewController. But if you save all data as instance variables it gets a bit ugly because first you have to get all data from the Recipe, save it to instance variables, and when you are done you have to do the reverse.
That's why I usually use a different approach. I use an editing context for editing (or adding, which is basically just editing).
- (void)presentRecipeEditorForRecipe:(MBRecipe *)recipe {
NSManagedObjectContext *editingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
editingContext.parentContext = self.managedObjectContext;
MBRecipe *recipeForEditing;
if (recipe) {
// get same recipe inside of the editing context.
recipeForEditing = (MBRecipe *)[editingContext objectWithID:[recipe objectID]];
NSParameterAssert(recipeForEditing);
}
else {
// no recipe for editing. create new one
recipeForEditing = [MBRecipe insertInManagedObjectContext:editingContext];
}
// present editing view controller and set recipeForEditing and delegate
}
Pretty straight forward code. It creates a new children context which is used for editing. And gets a recipe for editing from that context.
You must not save the context in your EditViewController! Just set all desired attributes of Recipe, but leave the context alone.
After the user tapped "Cancel" or "Done" this delegate method is called. Which either saves the editingContext and our context or does nothing.
- (void)recipeEditViewController:(MBRecipeEditViewController *)editViewController didFinishWithSave:(BOOL)didSave {
NSManagedObjectContext *editingContext = editViewController.managedObjectContext;
if (didSave) {
NSError *error;
// save editingContext. this will put the changes into self.managedObjectContext
if (![editingContext save:&error]) {
NSLog(#"Couldn't save editing context %#", error);
abort();
}
// save again to save changes to disk
if (![self.managedObjectContext save:&error]) {
NSLog(#"Couldn't save parent context %#", error);
abort();
}
}
else {
// do nothing. the changes will disappear when the editingContext gets deallocated
}
[self dismissViewControllerAnimated:YES completion:nil];
// reload your UI in `viewWillAppear:`
}

RestKit 0.20 — What is the preferred way to create a new NSManagedObject?

I'm curious to know what the best way is to create a new NSManagedObject in RestKit 0.20? Currently my code looks something like this:
#pragma mark - navigation buttons
- (void)createButtonDidTouch
{
// create new album object
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
NSManagedObjectContext *parentContext = RKObjectManager.sharedManager.managedObjectStore.mainQueueManagedObjectContext;
context.parentContext = parentContext;
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Album" inManagedObjectContext:parentContext];
Album *newAlbum = [[Album alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:context];
// pass object to create view to manipulate
AlbumCreateViewController *createViewController = [[AlbumCreateViewController alloc] initWithData:newAlbum];
createViewController.delegate = self;
createViewController.managedObjectContext = context;
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:createViewController];
navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:navController animated:YES completion:nil];
}
#pragma mark - create view controller delegate
- (void)createViewControllerDidSave:(NSManagedObject *)data
{
// dismiss the create view controller and POST
// FIXME: add restkit code to save the object
NSLog(#"save the object...");
NSDictionary *userInfo = [KeychainUtility load:#"userInfo"];
NSString *path = [NSString stringWithFormat:#"/albums/add/%#/%#", userInfo[#"userID"], userInfo[#"apiKey"]];
[RKObjectManager.sharedManager postObject:data path:path parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
operation.targetObject = data;
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"create album error: %#", error);
}];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)createViewControllerDidCancel:(NSManagedObject *)data
{
// dismiss the create view controller
NSLog(#"delete the object...");
// FIXME: add restkit code to delete the object
[self dismissViewControllerAnimated:YES completion:nil];
}
I'm also curious to know what my responsibilities are for saving / deleting this object. If I POST to the server via RestKit is the managed object context saved?
What if I decide to cancel this creation process — what's the preferred way to delete this object?
Basically how much is RestKit doing for me, and what should I make sure I'm doing. I haven't found much documentation on this and would like to be clear on it.
When you initialize an RKManagedObjectRequestOperation for a given object, RestKit will obtain a permanent object ID for that object and then create a child managed object context whose parent context is the context the object is inserted into. The operation then executes the HTTP request to completion and obtains a response.
If the response is successful and the mapping of the response is successful (note that the mapping occurs within this private child context), then the private child context is saved. The type of save invoked is determined by the value of the savesToPersistentStore property (see http://restkit.org/api/0.20.0/Classes/RKManagedObjectRequestOperation.html#//api/name/savesToPersistentStore).
When YES, the context is saved recursively all the way back to the persistent store via the NSManagedObjectContext category method saveToPersistentStore (see http://restkit.org/api/0.20.0/Categories/NSManagedObjectContext+RKAdditions.html).
When NO, the context is saved via a vanilla [NSManagedObjectContext save:] message, which 'pushes' the changes back to the parent context. They will remain local to that context until you save them back. Keep in mind that managed object context parent/child hierarchies can be as long as you create within the application.
If the HTTP request failed or there was an error during the mapping process, the private context is not saved and the operation is considered failed. This means that no changes are saved back to the original MOC, leaving your object graph just as it was before the operation was started (except the object being sent, if temporary, now has a permanent object ID but is still unsaved).
The way you do it should works (calling each time the MOC in each of your VC), but is not "recommended".
What Apple suggests, just like any Core Data app, is the "pass the baton" style.
Nested contexts make it more important than ever that you adopt the
“pass the baton” approach of accessing a context (by passing a context
from one view controller to the next) rather than retrieving it
directly from the application delegate.
See here:
http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-CoreData/_index.html
As for your second question, RestKit should manage saving/updating your Core Data stack upon success of your api calls if everything is well mapped/setup.
From blake the RK creator:
if you POST or PUT a Core Data object, RK obtains a permanent object
ID for it and then creates a secondary managed object context, fires
the request, and maps the response (if possible). if the response and
the mapping are successful, it will either save it back to the parent
context or all the way back to the persistent store (i.e. into SQLite)
based on the value of the savesToPersistentStore.

NSArrayController without loading a large dataset into an array

I would like to use an NSArrayController to provide data to an NSTableView. The problem I am facing is that I do not want to pre-load all my data into an array and then use the array controllers setContent: method. My data model is a large existing code base that manages millions of records. It contains methods to efficiently return a set of data rows.
Following an example I found on limiting the number of objects in an NSArrayController, I tried subclassing NSArrayController and overriding the arrangedObjects: method to return an array proxy class I wrote. The array proxy class provided count: and objectAtIndex: methods. The object returned by objectAtIndex: is an NSDictionary. When I tried returning my array proxy from the arrangedObjects: method both count: and objectAtIndex: get called, but I also get an unrecognized selector error on my array proxy class for _valueForKeyPath:ofObjectAtIndex:. This looked like a private method, so I did not continue down this path.
I also thought of returning a smaller array of data from arrangedObjects:, but could not figure out how I would determine which rows the NSTableView was trying to display.
Is a datasource the "correct" way to interface with my existing data model or is there some way to make an NSArrayController work?
NSArrayController already works, with proxies and indexes and lazy-loading and the whole shabang. Have you tried just using it as-is? If afterwards you feel the need to micro-manage the data-loading, use NSFetchRequest. Subclass NSArrayController and add an initializer along these lines:
+ (id)arrayControllerWithEntityName: (NSString *)entityName error:(NSError **)error
{
id newInstance = [[[self alloc] initWithContent:nil] autorelease];
[newInstance setManagedObjectContext:[[NSApp delegate] managedObjectContext]];
[newInstance setEntityName:entityName];
[newInstance setAutomaticallyPreparesContent:YES];
[newInstance setSelectsInsertedObjects:YES];
[newInstance setAvoidsEmptySelection:YES];
[newInstance setAlwaysUsesMultipleValuesMarker:YES];
NSFetchRequest *dontGobbleRequest = [[[NSFetchRequest alloc] init] autorelease];
//configure the request with fetchLimit and fetchOffset an' all that
NSError *err;
if ([newInstance fetchWithRequest:dontGobbleRequest merge:YES error:&err] == NO) {
//better check docs whether merge:YES is what you want
if(*error && err) {
*error = err;
}
return nil;
}
return newInstance;
}
You'll have to do some research into the various possibilities and configurations, but you get the picture.

Resources