To pre-populate my Database for a small Iphone-App I do a check in the persistentStoreCoordinator Method in the AppDelegate if the sqlite-file is already there or not. If not I add some data into a table after the db is created. But the data should not directly shown in the RootViewController but in another TableView which can be accessed by pressing a Option in the RootViewController.
The Problem is that the data are saved but are not shown in the TableView. But if terminate and restart the app the data are there.
I have passed the managedObjectContext from my appDelegate to the the RootViewController and from there to the TableViewController. So the managedObjectContext is the same everywhere.
Here is the Code where I pre-populate the DB (in this example just one sample row):
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"TestDBApp.sqlite"];
NSString *storePath = [storeURL relativePath];
BOOL noDb = false;
NSFileManager *fileManager = [NSFileManager defaultManager];
if( ![fileManager fileExistsAtPath:storePath]) {
noDb = true;
}
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
if( noDb) {
noDb = false;
[self populateDB];
}
return persistentStoreCoordinator_;
}
- (void) populateDB {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Kunde" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"oxid" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self insertNewKunde:aFetchedResultsController];
[self saveContext];
[aFetchedResultsController release];
[sortDescriptors release];
[sortDescriptor release];
[fetchRequest release];
}
- (void)insertNewKunde:(NSFetchedResultsController *)fetchedResultsController {
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
// If appropriate, configure the new managed object.
[newManagedObject setValue:[[NSDate date] description] forKey:#"oxid"];
[newManagedObject setValue:#"Max" forKey:#"fname"];
[newManagedObject setValue:#"Mustermann" forKey:#"lname"];
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
The managedObjectContext is passed from the AppDelegate to the RootViewController as:
- (void)awakeFromNib {
RootViewController *rootViewController = (RootViewController *)[navigationController topViewController];
rootViewController.managedObjectContext = self.managedObjectContext;
}
and to the other TableViewController:
- (void)awakeFromNib {
kundenVC = [[KundenViewController alloc] initWithManagedObjectContext:self.managedObjectContext];
}
Can anyone help me or give a hint?
I also tried to reload the tableView through the viewWillAppear-Method. But no effect.
It is very difficult to guess what is happening but my hunch is that you are not calling self.populateDB until the RootViewController is actually created. This means the RootViewController does not see that data because it is not created.
I could be wrong but I guess that your last line in delegate's awakeFromNib method is eventually calling populateDB through self.managedObjectContext -> persistentStoreCoordinator -> self.populateDB.
Please try to add a line right at the top of awakeFromNib looking like this:
(void)awakeFromNib {
NSManagedObjectContext *context = self.managedObjectContext;
RootViewController *rootViewController = (RootViewController *)[navigationController topViewController];
rootViewController.managedObjectContext = context;}
This way I hope the your problem is solved because self.managedObjectContext will call the getter (- (NSManagedObjectContext)managedObjectContext;) instead of the field. Inside there (if you use the generated code) there is a call to the getter of ** persistentStoreCoordinator** which eventually calls you populateDB method.
In case that does not work go ahead and set a breakpoint inside your populateDB. When the debugger stops you should see at the method call stack where you populateDB is actually called from and that might help to figure out your problem.
-Andy
Well, the important part you left out to answer your question. What is [self saveContext] and how are you displaying the records in your table view.
Because your data is saved I assume that the code given here works. The only guess I can make is to use reset on the NSManagedObjectContext and then refetch.
BTW I like the "insertNewKunde" which seems to be the new German grammar ;-)
Related
I am writing an iphone app; I need to save the data to persistent storage, but I am having the problems...
I created an entity in xcdatamodel and I have no problem getting the default value set for my attribute. In my testing, I go thru the code several times below and can change this attribute (it's an integer) while the application is active. However, when I close and reopen the app, my integer (myInt) is back to the default value when I expected it to be the last value I updated it to.
Here is what I am doing:
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSEntityDescription *myTestEntity = [NSEntityDescription
entityForName:#"TestEntity"
inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:timeStampEntity];
NSError *error = nil;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// TODO: Handle the error
}
for (id oneObject in fetchedObjects)
{
NSInteger myInt = [[oneObject valueForKey:#"myStoredInt"]intValue];
myInt++;
[oneObject setValue:[NSNumber numberWithInt: myInt] forKey:#"myStoredInt"];
NSError *error = nil;
if (![managedObjectContext save:&error]) {//TODO: Handle error
}
}
[fetchRequest release];
I was hoping that line:
if (![managedObjectContext save:&error])
would do the job and store the changes to the persistent storage, but it does not! (Having this line inside or outside the for loop does not change the result)
What am I doing wrong?
Thank you very much for your help!
It will be helpful if you could show your app delegate code. My guess is that somewhere you are reseting the data your self. maybe in the method that you load the start data.
3 notes for now:
you don't need to place the save
method inside the loop. you should
place it after all the changes are
made and just once.
check if you have a save method called in the (if not add them):
- (void)applicationWillTerminate:(UIApplication*)application {
[self saveContext];
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
[self saveContext];
}
if it dose not help try to see if you are loading the default values on every lunch of the app.
GOOD LUCK
and welcome
EDIT
Hey, you can do that -
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"anyname.sqlite"]];
//check if the store already exist?
BOOL firstRun = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path] isDirectory:NULL]) {
firstRun = YES;
}
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
if(firstRun){
//add your default data here
}
}
I'm a little stumped with an issue that I think goes back to my design.
I'm constructing a TableViewController based on a mainly static set of rows (4) - using that as a basis for a UITableView. Each row will kick off varying different views (detail, and UITableViews)... In my managed object context for the top view I can easily nav to the associated detail view because it's in context (nickname).. I initially thought about simply having unrelated tables and a series of buttons that would fire off the views... But I ditched that idea. My main current issue is knowing how to switch MOC or to different fetchedresults controllers (which are by the way defined in each .m to fetch from different tables.
At the moment, I've got a switch inside of TableViewControler.m.. This snipit works:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger switchval = indexPath.row;
switch (switchval) {
case 0: // Nickname
{
//This version brings up a detailed view controller -
NickNameDetail *controller = [[NickNameDetail alloc] initWithNibName:#"NickNameDetail" bundle:nil];
controller.fetchedResultsController = self.fetchedResultsController;
controller.managedObjectContext = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.navigationController pushViewController:controller animated:YES];
break;
}
case 1:
{
//NextTableViewContoller *.... (etc)... here here
...
However, I can't figure out how to switch to a different context and get different rows.
meTableViewController.managedObjectContext = self.managedObjectContext;
nickNameDetail.managedObjectContext = self.managedObjectContext;
nextTableViewController.managedObjectContext = self.managedObjectContext;
Anyone run into a scenario like this before? Sorry, can't get the code formatter to behave.
Thanks!
In some cases it may be good/opportune to pass to the new view controller pushed on the navigation stack the MOC of your current view controller. However, you usually want to pass a newly created MOC. Do this as follows:
in your app delegate add the following method
- (NSManagedObjectContext*)createNewManagedObjectContext
{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
return [moc autorelease];
}
in your view controller pass the MOC as follows
myAppDelegate *mainDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainMOC = [mainDelegate createNewManagedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:mainMOC];
newViewController.managedObjectContext = mainMOC;
and then handle the notification as needed, here is an example
- (void)contextDidSave:(NSNotification *)notification
{
[managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
[self.tableView reloadData];
}
You also need to define and use a different NSFetchedResultsController for each of your view controllers. This is because the data you want to fetch and display are, of course, different for each view controller (different entity, predicate etc). Simply define them in each implementation file. Here is an example:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Context" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
if(self.project){
// get the contexts for the project
NSPredicate * predicate = [NSPredicate predicateWithFormat: #"projectName == %#", self.project.name];
[request setPredicate:predicate];
}
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request 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:request managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[request release];
[sortDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}
and then use the fetchedResultsController as needed. For instance, put this in your viewDidLoad method:
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
I am not sure if the leak is in my implementation or is it from apple's side....
Instruments indicate me that I have a leak in this line :
if (![[self fetchedResultsController]
performFetch:&error])
I am adding annotations by reading the fetchController to the Map.... like this :
-(void)fillMapWithAnnotations{
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
for(int a=0; a<[[[fetchedResultsController sections]objectAtIndex:0] numberOfObjects]; a++){
LookAround *look=(LookAround *)[fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:a inSection:0]];
if(look){
AddAnnotation *newAnnotation=[[AddAnnotation alloc]initWithLookAround:look];
if(newAnnotation){
[self.mapView addAnnotation:newAnnotation];
[newAnnotation release];
newAnnotation=nil;
}
}
}
}
and I initialize my FetchController like this:
- (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:#"LookAround" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" 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:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
}
return fetchedResultsController;
}
I get a leak as soon as i Navigate Back, the ViewController gets Deallocated in which I release my fetch controller object.
The objects that leak are numerous (and of the same type I guess) around the number of records in my sqlite DB
Thanks in advance for your help....
As I noted above, the leak is probably in your AddAnnotation class.
I have an app which is a UITabBarController, I have defined two subviews
Both tabs have their Class attribute in the Identity Inspector set to UINavigationController.
Now i have managed to get this far with my coding after VERY LONG trials.
- (void)viewDidLoad {
[super viewDidLoad];
myAppDelegate *appDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = appDelegate.managedObjectContext;
{
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:self.managedObjectContext]];
NSArray *fetchedItems = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSEntityDescription *entityDesc =
[NSEntityDescription entityForName:#"User" inManagedObjectContext:self.managedObjectContext];
// replace the old data with new. this DOESNT WORK
if (fetchedItems.count > 0)
{
Usr *newUsr;
for (newUsr in fetchedItems)
{
if ([newUsr.name isEqualToString:#"Line One"])
{
newUsr.uName = #"Line One (new)";
}
}
}
//add a new default data. THIS ADDS DATA TO MY TABLEVIEW BUT IT DOESNT SAVE THEM TO THE SQLITE
User *addedDefaultdata = nil;
addedDefaultdata = [[User alloc] initWithEntity:entityDesc insertIntoManagedObjectContext:self.managedObjectContext];
addedDefaultdata.name = #"Added new 1";
[addedDefaultdata release];
}
NSError *error = nil;
if (![User.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
and my appdelegate looks like this:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[application setStatusBarStyle:UIStatusBarStyleBlackOpaque];
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
}
now I cannot quire the "User" at all! although i get no errors or warnings!
Any suggestions would be much appreciated!
Thanks
It seems that you may be asking how to update to CoreData?
If so, you need to use the save: method on NSManagedObjectContext, like this:
NSError *error;
[managedObjectContent save:&error];
if (error) {
...
}
I'm trying to write an application with Core Data, and I have been able to successfully read and write to the core data database. However, if I write to the database in one view controller, my other view controllers will not see the change until the app is closed then reopened again. This is really frustrating. I'm not entirely sure how to get the refresh - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag method to work. How do I get a reference to my managed object?
Anyways, here's the code I'm using to read the data back. This is in the viewDidLoad method.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Website" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"siteName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if(mutableFetchResults == nil) {
//Handle the error
}
[self setNewsTitlesArray:mutableFetchResults];
[mutableFetchResults release];
[request release];
[newsSourcesTableView reloadData];
Thanks for help in advance!
I am not entirely sure what do you intend to do from what I understand you are changing your managed object context in one view controller and you want the result to be visible in other view controllers is this correct?. Anyway a solution for this is to listen for the NSManagedObjectContextDidSaveNotification (which is send when the context is saved) and register as a observer the view controller you want to be afected by the changes:
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:myController selector:#selector(updateTable:) name:NSManagedObjectContextDidSaveNotification object:controller.context];
The updateTable in the myController Controller the selector could look something like this:
- (void)updateTable:(NSNotification *)saveNotification
{
if (fetchedResultsController == nil)
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
//Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
else
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
[self.tableView reloadData];
}
Hope that helps.
-Oscar
Actually, what I found was that I had the table set to load as my view. When I put the table into another view, everything worked fine.