ios5 core data: nsfetchresultcontroller refresh uitable - core-data

i'm working on an app with core data with storyboard. the app has uitabbarcontroller as rootview. i have created entity and generated the classes. each tab has it own uinavigation controller. the view in the tab 1 just saves some data in the database from uilabels. and it works fine and data is in the database.
the view in tab 2 displays the data from the database in uitableview. the data is only shown when i kill the app and restart it. so the ui table doesnt get refreshed.
first method: i have passed the managedobject context from the app delegate to the both views. so ui table doesnt get refreshed till kill and restart.
second method: i (mis)used the app delegate, but still the same result.
MyApplicationDelegate *appDelegate = (MyApplicationDelegate *)[[UIApplication sharedApplication] delegate];
how can one achieve that one view only adds data to core data(which it does right now) and the second view get notified of changes and display it in uitableview?
edit
-(NSFetchedResultsController *) fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Favis" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"chapterid" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[NSFetchedResultsController deleteCacheWithName:#"Master"];
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]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
it is the code when you use the core data template. only tweaked to work with my app. i have it in my both viewcontroller.
edit 2
i have implemented nsfetchedresultcontroller in my uitableview controller.
the manged object returns the exact number of data in the database, but ui table doesnt get refreshed. i also did [self.tableview reloaddata] but no luck

In the viewController with the UITableVIew implement the methods for the NSFetchedResultsControllerDelegate. The documentation has the full implementation of those methods.
And then make your viewController the delegate of the NSFetchedResultsController fetchedResultsController.delegate = self;

There should be some thing in 2 tab as to notify as data changed in database update the new data, Is there any? If
NSManagedObjectContext, NSFetchedResultsController in 2 tab by saying
Implement NSFetchedResultsController delegation methods.
in appdelegate
secTab.managedObjectContext = self.managedObjectContext;
Surely it works now

Related

Saving array of images in core data as transformable

I want to add my imageArray into coredata as transformable but this is not storing properly.
My save button coding.
- (IBAction)saveButton:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"FoodInfo" inManagedObjectContext:context];
NSAttributeDescription *messageType = [[NSAttributeDescription alloc] init];
[messageType setName:#"photos"];
[messageType setAttributeType:NSTransformableAttributeType];
[imagesForShow addObject:messageType];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unable to save context for class" );
} else {
NSLog(#"saved all records!");
[context save:nil];
}
//[newEntry setValue:imagesForShow forKey:#"images"];
}
Here 'imagesForShow' is my array of images.
When iam going to fetch this image array , this showing nil
- (void)viewDidLoad {
[super viewDidLoad];
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:#"FoodInfo"];
[request setReturnsObjectsAsFaults:NO];
arrayForPhotos = [[NSMutableArray alloc]initWithArray:[context executeFetchRequest:request error:nil]];
// Do any additional setup after loading the view.
}
What I am doing wrong with this code. Thanks.
In your save code:
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *newEntry = [NSEntityDescription
insertNewObjectForEntityForName:#"FoodInfo"
inManagedObjectContext:context];
NSAttributeDescription *messageType = [[NSAttributeDescription alloc] init];
[messageType setName:#"photos"];
[messageType setAttributeType:NSTransformableAttributeType];
[imagesForShow addObject:messageType];
I can't even figure out what this is supposed to do. It's completely wrong. You should never be allocating an instance of NSAttributeDescription unless you are constructing a Core Data model on the fly-- which you are not doing and which almost nobody ever does. Creating the new entry is OK. The rest, I don't know. You said that imagesForShow is your array of images, so I don't know why you're also adding an attribute description to the array.
In a more general sense, if newEntry has a transformable attribute named photos and imagesForShow is an NSArray of UIImage objects, then you could do this:
[newEntry setValue:imagesForShow forKey:#"photos"];
This is similar to a line that you have commented out, though it's not clear why it's commented out.
But whatever you do get rid of the code creating the NSAttributeDescription.

CoreData objects not getting unfaulted

In an iOS6 app I fetch NSManagedObjects from DB with CoreData and display them in a tableViewCell. My problem is, that all objects that correspond to cells outside of the initial scroll position are in fault state and do not come back to life. I can't see my mistake.
fetchRequest.returnsObjectsAsFaults = NO; helps, but I want a clean solution.
Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ContactsCell";
ContactsCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Contact *contact = [self.contacts objectAtIndex:indexPath.row];
//here some contacts are faulted. contact.name is null if I fetch it
[cell setContactData:contact];
return cell;
}
here is how I fetch (with Restkit 0.10.3):
NSFetchRequest *fetchRequest = [Contact fetchRequest];
fetchRequest.sortDescriptors = [self sortDescriptors];
fetchRequest.returnsObjectsAsFaults = NO;
return [Contact objectsWithFetchRequest:fetchRequest];
Ok I never really used your approach so I won't say that it is wrong, but I'll say that it is strange. I guess that Contact is a subclass of NSManagedObject, and I can believe that it knows of the fetch request which which he was originally fetched, and that he knows of the context from which he was fetched, but only after he was already fetched. I really don't see how could he know of those things if he never before was fetched from the persistent store. So I recommend U use classic executeFetch or fetchedResultsController to populate your tableView.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:persistentStoreCoordinator];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contact" inManagedObjectContext:context];
fetchRequest.entity = entity;
fetchRequest.sortDescriptors = [self sortDescriptors];
NSArray *array = [context executeFetchRequest:fetchRequest error:&error];
return array;
Try it, hope it helps

A Core-Data Relashionship not working after stopping the app

I have had one issue with Core Data and Relationship. Since this has kept me without a solution for a while and has at the same time been easy to locate I have made a tiny sample application to reproduce the problem.
Under XCode I created a barebone Window-based application, checking "Use Core Data for storage".
I called this application "CDR" for Core-Data-Relationship.
I then added a subclass of UIViewController called CDR_ViewController; as I usually do.
Here is the relevant code that I added :
First in CDR_ViewController.h :
#import <UIKit/UIKit.h>
#import "AppDelegate_Shared.h"
#interface CDR_ViewController : UIViewController {
UILabel *cdrLabel;
NSManagedObject *currentItem;
}
#property (nonatomic, retain) IBOutlet UILabel *cdrLabel;
-(IBAction) handleButtonClick:(id)sender;
#end
Then in CDR_ViewController.m the viewDidLoad method is as follow :
- (void)viewDidLoad {
[super viewDidLoad];
NSFetchRequest *request;
NSError *error;
AppDelegate_Shared *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
request=[[NSFetchRequest alloc] init];
[request setEntity: [NSEntityDescription entityForName:#"CDR_Entity" inManagedObjectContext:context]];
error=nil;
NSUInteger count = [context countForFetchRequest:request error:&error];
[request release];
if (count!=0) {
request=[[NSFetchRequest alloc] init];
[request setEntity: [NSEntityDescription entityForName:#"CDR_Entity" inManagedObjectContext:context]];
error=nil;
NSArray *objects=[context executeFetchRequest:request error:&error];
NSLog(#"Error:%#",error);
[request release];
currentItem=[objects objectAtIndex:0];
return;
}
NSManagedObject *newItemOne,*newItemTwo,*newItemThree;
request=[[NSFetchRequest alloc] init];
[request setEntity: [NSEntityDescription entityForName:#"CDR_Entity" inManagedObjectContext:context]];
newItemOne=[NSEntityDescription insertNewObjectForEntityForName:#"CDR_Entity" inManagedObjectContext:context];
newItemTwo=[NSEntityDescription insertNewObjectForEntityForName:#"CDR_Entity" inManagedObjectContext:context];
newItemThree=[NSEntityDescription insertNewObjectForEntityForName:#"CDR_Entity" inManagedObjectContext:context];
[newItemOne setValue:[NSNumber numberWithInteger:1] forKey:#"Value"];
[newItemTwo setValue:[NSNumber numberWithInteger:2] forKey:#"Value"];
[newItemThree setValue:[NSNumber numberWithInteger:3] forKey:#"Value"];
[newItemOne setValue:newItemThree forKey:#"Previous"];
[newItemOne setValue:newItemTwo forKey:#"Next"];
[newItemTwo setValue:newItemOne forKey:#"Previous"];
[newItemTwo setValue:newItemThree forKey:#"Next"];
[newItemThree setValue:newItemTwo forKey:#"Previous"];
[newItemThree setValue:newItemOne forKey:#"Next"];
error=nil;
[context save:&error];
[request release];
currentItem=newItemOne;
}
And the viewWillAppear method is as follow :
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
cdrLabel.text=[NSString stringWithFormat:#"%#",[currentItem valueForKey:#"Value"]];
}
Finally the handleButtonClick method is as follow :
-(IBAction) handleButtonClick:(id)sender
{
if (((UIButton*)sender).tag==101) {// Previous item.
currentItem=[currentItem valueForKey:#"Previous"];
} else /*(((UIButton*)sender).tag==102)*/ {// Next item.
currentItem=[currentItem valueForKey:#"Next"];
}
cdrLabel.text=[NSString stringWithFormat:#"%#",[currentItem valueForKey:#"Value"]];
}
The CDR_ViewController.xib contains one Label and two buttons.
This code works fine for start, meaning just after I compile the app and reset the contents of the iPhone simulator.
I can then cycle the contents of the label : 1,2,3,1,2,3,1,2,3 ---etc… and backward with the buttons.
As soon as I terminate the application using Command-Q. When I want to start it again, it crashes on :
currentItem=[currentItem valueForKey:#"Previous"];
or:
currentItem=[currentItem valueForKey:#"Next"];
inside the handleButtonClick method.
And it is the same when I put the app on my iPod touch.
Can anyone see in my code anything that could explain this behavior?
If you terminate an app in the simulator by stopping it, a opposed to clicking the home button in the simulator or otherwise, then the program is sent a SIGKILL message which immediately stop it running. Your application delegate methods (will terminate, will enter background etc) will not be called.
In all likelihood this will have meant that your managed object context has not been saved, so when re-running the app the object graph cannot be restored properly. In your sample app, add another button which saves the context. If you click this button, then terminate your app, does that solve the problem?

Retrieving data while multi threading

I am using multithreading while loading data from the database.
I am doing the following
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
// Get data
NSDate *pastDate = [CommonHelper getSelectedDateYearBackComponents:[NSDate date]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"users == %# && startDate >= %#", objUser,pastDate];
NSMutableArray *fetchArray = [DataAccess searchObjectsInUserContext:#"userIngo" :predicate :#"startDate" :NO];
if ([fetchArray count] > 0)
{
dispatch_async(dispatch_get_main_queue(),
^{
// Reload table
[self.tableView reloadData]; });
}
else
{
dispatch_async(dispatch_get_main_queue(),
^{ // calling Webservice
});
}
});
where users is the entity from which I am trying to fetch data and objUser is the user object for whom I am retrieving data from the users entity
and my searchObjectsInUserContext code is like this
+(NSMutableArray *) searchObjectsInLabContext: (NSString*) entityName : (NSPredicate *) predicate : (NSString*) sortKey : (BOOL) sortAscending
{
i3EAppDelegate *appDelegate = (i3EAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setUndoManager:nil];
[context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[request setEntity:entity];
[request setFetchBatchSize:10];
[request setReturnsObjectsAsFaults:NO];
// If a predicate was passed, pass it to the query
if(predicate != nil)
{
[request setPredicate:predicate];
}
// If a sort key was passed, use it for sorting.
if(sortKey != nil)
{
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
}
NSError *error;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
// NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[request release];
appDelegate = nil;
return [mutableFetchResults autorelease];
}
So in my searchObjectInUserContext I use multiple managedObjectContext so that it does not create problems for me while multithreading as explained by Fred McCann in his blog post.
However, my problem is at NSMutableArray *mutableFetchResults in my searchObjectsInUserContext because it returns 0 at times even though there is data in the database.
Can someone help me with what I am doing wrong?
You are leaking the context you create; you never release it.
There is no need to register as an observer because you are never saving with that context.
Why are you making a mutable copy of the context? That rarely serves any useful purpose.
How do you know there is data in the database?
Is this method being run on a background thread?
Update
There is nothing wrong with your fetch so I suspect the issue might be one of timing. Is this fetch being run before another thread saves? That would explain the behavior you are seeing.
Also, why are you running this on a background thread? Are you seeing a performance issue that requires a background search like this?
Update 2
First, I still question the need for the background fetching. That is normally reserved for when you have performance issues as fetching is very fast.
Second, you fetch on a background queue but then you don't do anything with the data you fetched. You do not hand it off to the table (which would be bad anyway) you just fetch and throw it away.
If you are fetching just to count then don't fetch, do a -[NSManagedObjectContext -countForFetchRequest: error:]. It will be even faster and removes the need for the background queue call.
If you are expecting to do something with the results you have an issue. Instances of NSManagedObject cannot cross a thread/queue boundary. So fetching on the background queue does very little for the UI. You need to fetch those objects on the main queue/main thread. If you don't you will crash because Core Data does not allow it.
In the end, your background queues are doing you more harm than good and they are not solving the problem you are trying to solve.

NSFetchResultsController + sectionNameKeyPath + section order

I have a one to many entity relationship between two entities:
EntityP (Parent) <-->> EntityC (Child)
Attributes and Relationships:
EntityP.title
EntityP.dateTimeStamp
EntityP.PtoC (relationship)
EntityC.title
EntityC.dateTimeStamp
EntityC.CtoP (relationship) // Can be used to get "one" EntityP record
I use fetch results controller to show the results. Here's my implementation of the fetch results controller:
#pragma mark -
#pragma mark Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create the fetch request for the entity
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Set Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityC" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set Predicate
// (Ignore, we want to get list of all EntityC records)
// Set Sort Descriptors (sort on Parent - for section, and then on Child - for rows)
NSSortDescriptor *sortDescriptorPDate = [[NSSortDescriptor alloc] initWithKey:#"CtoP.dateTimeStamp" ascending:YES];
NSSortDescriptor *sortDescriptorDate = [[NSSortDescriptor alloc] initWithKey:#"dateTimeStamp" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorPDate, sortDescriptorDate, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:20];
// Create and initialize the fetch results controller
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"CtoP.title" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
// Cleanup memory
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptorPDate release];
[sortDescriptorDate release];
[sortDescriptors release];
NSError *error = nil;
if (![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;
}
Now, for example, if i have following data in the persistence store:
EntityP.dateTimeStamp EntityP.title EntityC.dateTimeStamp EntityC.title
Today B Today d
Yesterday A Yesterday a
Today B Yesterday c
Yesterday A Today b
Note: Yesterday and Today is in NSDate format.
Then i should get the sections and rows in following order (exactly):
A
a
b
B
c
d
But, the sort is not working like this. I'm getting the rows in correct order, but the sections are not ordered! I hope sortDescriptorPDate is doing his job. What am i missing? Thanking in anticipation.
Not sure I understand your setup but...
By default the section titles are the capitalized first letter of the section name. If you want a custom sections such as ones based on dates, you will need to subclass NSFetchedResultsController and override the various sectionIndex... methods to return the correct sections, section indexes and section titles.
This seems to be working now! I'm not sure what changed! – Mustafa Sep 6 '10 at 4:03

Resources