I am having issues trying to get my CoreData to save, I have used CoreData before and don't normally run in to these issues. Can someone tell me what is wrong with the following code:
MapEntity *mapEntity = [_fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
NSMutableSet *set = (NSMutableSet *)mapEntity.points;
[set removeObject:self.selectedPoint];
self.selectedPoint = nil;
[[Singleton sharedSingleton] saveContext];
To initialise my fetched results controlled I use the following:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MapEntity" inManagedObjectContext:[[Singleton sharedSingleton] managedObjectContext]];
So the entity returned should definitely be in the singletons manabged object context. The code for saveContext is below:
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
NSLog(#"Unable to save context");
}
}
}
If I check the mapEntity.points after the removal of the object I can see the object has been removed. The object doesn't re-appear until I relaunch the app, so it must be a persistance issue. Can anyone figure out what's wrong with my code as I am completely baffled.
I have managed to fix the issue, seems it lied within the way I was deleting objects. Instead I am now using the set deletion functions created by CoreData, code below for anyone else with the same issues:
MapEntity *mapEntity = [_fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
NSSet *removeSet = [NSSet setWithObject:self.selectedPoint];
[mapEntity removePoints:removeSet];
self.selectedPoint = nil;
[[Singleton sharedSingleton] saveContext];
Related
I am creating an application using Microsoft Azure web service. On clicking logout button I need to clear my local core data base completely and all data will be reloaded after logging in again using the same username and password. To delete core data I am using below method
- (void) resetApplicationModel {
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = delegate.managedObjectContext;
for (NSPersistentStore *store in delegate.persistentStoreCoordinator.persistentStores) {
NSError *error;
NSURL *storeURL = store.URL;
NSLog(#"storeURL: %#", storeURL);
NSPersistentStoreCoordinator *storeCoordinator = delegate.persistentStoreCoordinator;
[storeCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error];
NSLog(#"There are errors: %#", error);
}
delegate.persistentStoreCoordinator = nil;
context = nil;
delegate.managedObjectModel = nil;
}
This is deleting all my data but when I am trying to login again without closing the application it is giving me following error
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This NSPersistentStoreCoordinator has no persistent stores (unknown). It cannot perform a save operation.'
This is occurring because while deleting data I am setting persistentStoreCoordinator to nil and its instance is being created inside AppDelegate file. Can somebody suggest me any solution for this?
Thanks for help in advance,.
Your code has a number of errors.
Regardless, to achieve what you need, try this modified version of your method resetApplicationModel...
- (void) resetApplicationModel {
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// NSManagedObjectContext *context = delegate.managedObjectContext;
for (NSPersistentStore *store in delegate.persistentStoreCoordinator.persistentStores) {
NSError *error;
NSURL *storeURL = store.URL;
NSLog(#"storeURL: %#", storeURL);
// NSPersistentStoreCoordinator *storeCoordinator = delegate.persistentStoreCoordinator;
// [storeCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error];
NSLog(#"There are errors: %#", error);
}
// delegate.persistentStoreCoordinator = nil;
// context = nil;
// delegate.managedObjectModel = nil;
}
The intention is you remove the lines of code that are commented out.
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.
how does my fetchedResultsController method look like, if I want to fetch all my attributes for an entity from core data? I only know and understand how to fetch data for a tableView and I think that is where all my confusion is coming from.
Here is my Core-Data setup:
I'm trying to fill an array with all the Attributes my Setting entity has and the show those values via NSLog output in my debug console.
Here is what I changed so far:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"Setting" inManagedObjectContext:context];
//NSManagedObject *newSetting = [NSEntityDescription insertNewObjectForEntityForName:#"Setting" inManagedObjectContext:context];
[newEntry setValue: #"StudiSoft" forKey:#"settingName"];
if (_overrideSysTimeSwitch.on) {
[newEntry setValue: #YES forKey:#"settingSysTimeOverride"];
//editSetting.settingSysTimeOverride = #YES;
NSLog(#"IF A");
} else {
//[newEntry setValue: #NO forKey:#"settingSysTimeOverride"];
//editSetting.settingSysTimeOverride = #NO;
NSLog(#"IF B");
}
if (_timeFormatSwitch.on) {
//[newEntry setValue: #YES forKey:#"settingTimeFormat"];
//editSetting.settingTimeFormat = #YES;
NSLog(#"IF C");
} else {
//[newEntry setValue: #NO forKey:#"settingTimeFormat"];
//editSetting.settingTimeFormat = #NO;
NSLog(#"IF D");
}
[self.settingsArray addObject:#"StudiSoft"];
NSError *error;
[context save:&error];
I'm using this code-snipped that and I'm able to modify the core data content.
However, every time I run this code, it of course adds a new object.
I've been looking for a way to update existing Attributes in my Entity, or modify them, but I could NOT find them.
Anyhow this is a good step into the right direction.
I created a completely new project, with just one view, once I have it working on the main view I'm going to experiment with segues....
But for now, how would I update or change existing attributes?
Thanks guys!!
This is my editSave Method to store some data in core data:
- (IBAction)editSave:(UIBarButtonItem *)sender
{
if ([_editSaveButton.title isEqualToString:#"Edit"])
{
[self setTitle:#"Edit Settings"];
//self.title = #"Edit Settings";
_overrideSysTimeSwitch.userInteractionEnabled = YES;
_timeFormatSwitch.userInteractionEnabled = YES;
_editSaveButton.title = #"Save";
} else if ([_editSaveButton.title isEqualToString:#"Save"])
{
[self setTitle:#"Settings"];
//self.title = #"Settings";
_overrideSysTimeSwitch.userInteractionEnabled = NO;
_timeFormatSwitch.userInteractionEnabled = NO;
_editSaveButton.title = #"Edit";
// #############################################################
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
//NSManagedObject *newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"Setting" inManagedObjectContext:context];
//[newEntry setValue: #"StudiSoft" forKey:#"settingName"];
/*NSString *firstName = [anEmployee firstName];
Employee *manager = anEmployee.manager;
Setting *newSetting = [NSString #"Test"];
[newSetting setValue:#"Stig" forKey:#"settingName"];
[aDepartment setValue:[NSNumber numberWithInteger:100000] forKeyPath:#"manager.salary"];*/
//editSetting.settingName = #"Test";
if (_overrideSysTimeSwitch.on) {
//[newEntry setValue: #YES forKey:#"settingSysTimeOverride"];
editSetting.settingSysTimeOverride = #YES;
NSLog(#"IF A");
} else {
//[newEntry setValue: #NO forKey:#"settingSysTimeOverride"];
editSetting.settingSysTimeOverride = #NO;
NSLog(#"IF B");
}
if (_timeFormatSwitch.on) {
//[newEntry setValue: #YES forKey:#"settingTimeFormat"];
editSetting.settingTimeFormat = #YES;
NSLog(#"IF C");
} else {
//[newEntry setValue: #NO forKey:#"settingTimeFormat"];
editSetting.settingTimeFormat = #NO;
NSLog(#"IF D");
}
//[self.settingsArray addObject:#"StudiSoft"];
NSError *error = nil;
//if ([self.managedObjectContext hasChanges]) {
//NSLog(#"SAVE & DISMISS conetx has changed");
if (![context save:&error]) { // save failed
NSLog(#"Save failed: %#", [error localizedDescription]);
} else { // save succeeded
NSLog(#"Save Succeeded");
}
//}
//[self.tableView reloadData];
// #############################################################
}
}
Debug Output:
2014-06-10 19:09:29.881 SettingsCoreData[508:60b] Entry #5: <Setting: 0x8f983e0> (entity: Setting; id: 0x8f97030 <x-coredata://FA78AB86-3225-4B1E-97DD-3F31F5323A18/Setting/p6> ; data: {
settingName = StudiSoft;
settingSysTimeOverride = 0;
settingTimeFormat = 0;
})
2014-06-10 19:09:29.883 SettingsCoreData[508:60b] Entry #6: <Setting: 0x8f98430> (entity: Setting; id: 0x8f97040 <x-coredata://FA78AB86-3225-4B1E-97DD-3F31F5323A18/Setting/p7> ; data: {
settingName = StudiSoft;
settingSysTimeOverride = 1;
settingTimeFormat = 1;
})
Now I should be able to use something like this in my viewDidLoad, right?
if (editSetting.settingSysTimeOverride.boolValue == 0) {
_overrideSysTimeSwitch.on = NO;
} else {
_overrideSysTimeSwitch.on = YES;
}
But it doesn't work as I thought it will :-(
Next you need to call -performFetch: on the NSFetchedResultsController. Make sure you check the response and handle the error if the response is NO.
From there your NSFetchedResultsController is populated and ready to be used. You can then grab individual elements via -objectAtIndex: or you can grab them all with -fetchedObjects.
I would suggest just reviewing the documentation on the methods that are available as it has pretty strong and clear documentation.
Update
If you are not receiving any data then break it down. Take the NSFetchRequest that you created and call -executeFetchRequest:error: against your NSManagedObjectContext and see if you get any data back.
If you do then there is something wrong with your handling of the NSFetchedResultsController.
If you don't then there is something wrong with your NSFetchRequest or you don't have any data in your store.
Update
Sounds like you need to read a book on how Core Data works.
A NSFetchRequest is a query against Core Data so that objects can be returned from the store. You can pass a NSFetchRequest to a NSFetchedResultsController so that the NSFetchedResultsController can monitor the store for changes and let your view controller know when those changes occur.
A NSFetchRequest can also be executed directly against the NSManagedObjectContext and you can retrieve the results directly. You do that by calling -executeFetchRequest:error: against your NSManagedObjectContext and getting a NSArray back. You can then check that NSArray to see if you get any results.
If you do not understand that paragraph then you need to take a step back and read the tutorials on Core Data and/or read a book on Core Data. I can recommend an excellent book on the subject ;-)
I am performing an NSOperation on a background thread that imports data into Core Data. I do this by first creating a record of the import ('Import') and then import an object that relates to the import record. If I save the managed object context, the next attempt to link an imported object to the import record will fail:
Illegal attempt to establish a relationship 'import' between objects in different contexts (source = <NSManagedObject: 0x1067bb730> (entity: Genre; id: 0x1053330c0 <x-coredata:///Genre/tC6A85CFE-3D0A-4E29-9186-4FD46104AEBC60> ; data: {
import = nil;
name = Polka;
}) , destination = <NSManagedObject: 0x106736170> (entity: Import; id: 0x103b571e0 <x-coredata://440D80CF-7C56-4B6F-9778-990032A76B8B/Import/p1> ; data: <fault>))
Here is the boiled-down code. I modified the code slightly to demonstrate the effect by adding a superfluous save; normally there'd be no reason to have one there.
NSError *writeError = nil;
TNAppDelegate *del = (TNAppDelegate *)[[NSApplication sharedApplication] delegate];
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:[del persistentStoreCoordinator]];
[moc setUndoManager:nil];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
// create import instance
NSManagedObject *import = [NSEntityDescription insertNewObjectForEntityForName:#"Import" inManagedObjectContext:moc];
[import setValue:[NSDate date] forKey:#"start"];
[moc save:&writeError];
[moc reset];
NSString *newGenre = [songDictItem objectForKey:#"Genre"];
NSManagedObject *newGenreObject = [NSEntityDescription insertNewObjectForEntityForName:#"Genre" inManagedObjectContext:moc];
[newGenreObject setValue:newGenre forKey:#"name"];
[newGenreObject setValue:import forKey:#"import"]; // BOOM!
UPDATE: By request, I am providing the code for mergeChanges:. It is found in the NSOperation. I have tried a number of variations on saving changes to the main MOC, but they've all ended the same way.
- (void)mergeChanges:(NSNotification*)notification
{
TNAppDelegate *del = (TNAppDelegate *)[[NSApplication sharedApplication] delegate];
if ([notification object] == [del managedObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(mergeChanges:) withObject:notification waitUntilDone:YES];
return;
}
[[del managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
To this day I never figured out exactly what was going on. In the end I rebooted my project and designed it like this tutorial from the ground up. I had used it in the past for reference but somehow it took a full adoption of their code to work.
In upgrading my application from v1 to v2, I've made a few small changes to the Core Data Model. The changes are just adding new attributes to the models.
I have versioned the data model with the before and after changes and implemented the following code in my App Delegate:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"ISDEmployees.sqlite"];
NSLog(#"storeURL:%#",storeURL);
NSError *error = nil;
// Create a dictionary for automatic lightweight core data migration
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// Set up the persistent store and migrate if needed
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
Basically the standard persistentStoreCoordinator with the addition of the migration options. This code works great and my database is successfully updated. The problem that I'm having is that after the database update, I need to refresh all of the data in the database so that the new columns are populated. I was thinking that I would delete the data from the relevant entities/tables and force the application to re-download a new dataset with the added columns/attributes.
I'm not sure how/where to perform the delete/updates. The general application flow is this:
Log in with validation against an web API
On successful login, call the API and get latest added/updated records.
Display the updated data
I know I can check to see if a migration is needed by adding this code to persistentStoreCoordinator:
// Get the current data store meta data
BOOL migrationNeeded = NO;
NSDictionary *existingStoreData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeURL error:&error];
if (existingStoreData)
{
// Check to see if new model is not the same as the existing mode, meaning a migration is required
if (![self.managedObjectModel isConfiguration:nil compatibleWithStoreMetadata:existingStoreData])
{
migrationNeeded = YES;
}
}
Any help would be greatly appreciated!!
Update #1:
Based on the feedback below, I've made the following changes:
Changed the migrationNeeded from a local to a public class variable on the AppDelegate.
On the Login View, I've added the following method:
- (void)checkForDatabaseMigration
{
// Get a copy of the managed object context. If a migration is needed, it will kick it off
NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
if ([(AppDelegate *)[[UIApplication sharedApplication] delegate] migrationNeeded])
{
// Delete all data from the table
}
managedObjectContext = nil;
}
Does that seem right? The code works and the data is removed after migration and a fresh copy is inserted. I just hate to check for migration each time the application starts.
If you know how to determine when to delete old data, all you need is to fetch all the enteties you need and delete them. Here is how you do that(for example, if you want to delete all Man enteties):
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Man" inManagedObjectContext:myContext]];
[request setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * men = [myContext executeFetchRequest:request error:&error];
//error handling goes here
for (NSManagedObject * man in men) {
[myContext deleteObject:man];
}
NSError *saveError = nil;
[myContext save:&saveError];
//more error handling here