I've an NSArray in which I'm adding objects after user selects multiple rows from a tableview. After selecting user press confirm and it saves the data. So based on some example I found here I have implemented the code but it seems as it is only saving one value at a time. The last value that user selects:
- (IBAction)confirmPressed:(id)sender {
NSLog(#"Selected Are: %# - %#",selectedDX,selectedDesc);
for (NSString *code in selectedDX) {
if (!_dxToAddEdit) {
self.dxToAddEdit = [Diagnoses MR_createEntity];
}
[self.dxToAddEdit setCode:code];
[self.dxToAddEdit setCodeDescription:#"Sample Description"];
[self.dxToAddEdit setSuperBill:_forSuperBill];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
}
[self.navigationController popViewControllerAnimated:YES];
}
You are working only with one managed object self.dxToAddEdit. And it will contain the last code from array. If you want to save multiple objects you should do the following:
NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
for (NSString *code in selectedDX) {
Diagnoses *newDiagnose = [Diagnoses MR_createEntityInContext:defaultContext];
newDiagnose.code = code;
newDiagnose.codeDescription = #"Sample Description";
newDiagnose.superBill = _forSuperBill;
}
// Save recently created objects to persistent store.
[defaultContext MR_saveToPersistentStoreAndWait];
Related
I am currently saving a record using Magical Records as follows:
NSManagedObjectID *objID = [existingItem objectID];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
CustomNSMO *item;
if(newItem) item = [CustomNSMO MR_createEntityInContext:localContext];
else item = [localContext objectWithID:objID];
item.sync = 1;
item.relID = #"Some String";
}completion:^(BOOL success, NSError *error) {
//do something
}];
This works fine. If I query all items where sync has been set I get back the results I expect i.e.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(sync >= 1)"];
NSArray *objects = [CustomNSMO MR_findAllSortedBy:#"sync" ascending:YES withPredicate:predicate];
gives me an array of my new or updated objects.
I then (after some server business) want to find all results with sync >= 1 and set it to zero. This is when the strange behaviour occurs. I have been trying to get an array of results within a local context via either
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context];
NSArray *objects = [CustomNSMO MR_findAllSortedBy:#"sync" ascending:YES withPredicate:predicate inContext:localContext];
for(CustomNSMO *item in objects) {
item.sync = 0;
}
[localContext MR_saveToPersistentStoreAndWait];
or
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
NSArray *objects = [CustomNSMO MR_findAllSortedBy:#"sync" ascending:YES withPredicate:predicate inContext:localContext];
for (SyncObject *currentObj in objects)
{
currentObj.sync = #0;
}
} completion:nil];
If relID is set when the item is first created then this is all fine. I can edit the item (changing the relID) and the changes is observed in all contexts. However, if the first time the item is created it has a relID of nil then problems occur after using the code above to do the following:
1) Update the item changing a nil relID to some non-nil value
2) Save the item to the database
3) Load the item (by searching on 'sync')
In any context apart from MR_defaultContext relID stays as nil. If I look at the object in MR_defaultContext then I see the correct updated relID. If I look at it in a different context it is nil. This means that the update of sync on a local context overrides the value of relID on the default context (setting it to nil).
Some thoughts:
if(newItem) item = [CustomNSMO MR_createEntityInContext:context];
should be:
if(newItem) item = [CustomNSMO MR_createEntityInContext:localContext];
You don't want to use a different context inside +saveWithBlock:.
Also, this line:
else item = [localContext objectID];
I think you mean [localContext objectWithID:objID] or [localContext existingObjectWithID:objID error:error]?
Turns out it is a bug with the database store we are using below Core Data.
https://github.com/project-imas/encrypted-core-data/issues/118
I am trying to update a current CoreData object however when ever I use my write method its just adding another object to the database, so when I try to read the objects I get dozens of them back depending how long it's been used for.
Here is my code
#pragma mark -- Write
- (void)writeSeriesSearchObj:(NSMutableDictionary *)SearchDic Name:(NSString *)name
{
// WRITE TO CORE DATA
NSManagedObjectContext *context = [self managedObjectContext];
SeriesSearchObj *searchObj = [NSEntityDescription insertNewObjectForEntityForName:#"SeriesSearchObj" inManagedObjectContext:context];
if ([name isEqualToString:#"Code"]) {
searchObj.code = [SearchDic objectForKey:#"Code"];
} else if ([name isEqualToString:#"Name"]) {
searchObj.name = [SearchDic objectForKey:#"Name"];
} else if ([name isEqualToString:#"Model"]) {
searchObj.modelID = [SearchDic objectForKey:#"ModelID"];
}
NSError *error = nil;
[self.managedObjectContext save:&error];
// test
NSMutableArray *temptestA = [self readSearchObj];
NSLog(#"%#", temptestA);
}
I suspect I am going wrong using insertNewObjectForEntityForName however I am not sure how else to write this method in order for the same object to be updated every time?
SeriesSearchObj *searchObj = [NSEntityDescription insertNewObjectForEntityForName:#"SeriesSearchObj" inManagedObjectContext:context];
Will always return you a new object.
Use NSFetch to obtain the existing object and then update it.
I am building a very simple application with two entities: Person and Categories.
In my AddPersonViewController, I have a field for the person's name and a Table view with multiple selection enabled with predefined categories.
I would like to be able to select multiple categories and save them at once together with the person's name when I push the Save button.
I could find a lot of examples that save one related entity but no one for several at once.
EDITED
I select the categories and put them in an array then I save the user but it saves only the last category of the array.
Here is my save method:
- (IBAction)save:(id)sender {
//saves the user name
Users *name = [NSEntityDescription insertNewObjectForEntityForName:#"Users"inManagedObjectContext:self.managedObjectContext];
name.userName = addUserField.text;
NSError *error = nil;
if(![managedObjectContext save:&error]){
NSLog(#"Error! %#", error);
}
//saves the related categories
Kind *kind = [NSEntityDescription insertNewObjectForEntityForName:#"Kind"inManagedObjectContext:self.managedObjectContext];
for (int k=0; k < [_addCathegoryArray count]; k++) {
NSString *kindString = [[_addCathegoryArray objectAtIndex:k] description];
[kind setValue:kindString forKey:#"kindName"];
[name addHasKindsObject:kind];
}
if(![managedObjectContext save:&error]){
NSLog(#"Error! %#", error);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
Saving is done on the context, not on individual attributes or entities.
Put simply, when you save, all of the changes you've made to the objects in the context are saved.
I am learning how to use Core Data. I have an app that fills out all the variable of an entity labeled User. I then take these users and load them to a table. At this point the users can be selected and pushed to a new view controller at which point I generate a PDF file of the user selected. So I think I am misunderstanding what it is I have to pass to the view controller for it to access the core data selected in the table. Here is what I have in my table view controller.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.spinner startAnimating];
ReportPDFViewController *reviewViewController = [[ReportPDFViewController alloc] init];
reviewViewController.userInfo = [[self.fetchedResultsController fetchedObjects] objectAtIndex:indexPath.row];
[self.navigationController pushViewController:reviewViewController animated:YES];
}
Then the next view states this
- (void)viewDidLoad
{
[super viewDidLoad];
UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithTitle:#"Email PDF"
style:UIBarButtonItemStylePlain
target:self
action:#selector(emailPDF)];
self.navigationItem.rightBarButtonItem = barButton;
TrafficSignalProAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
context = [appDelegate managedObjectContext];
// Do any additional setup after loading the view.
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entitydesc];
NSError *error;
NSArray *matchingData = [userInfo executeFetchRequest:request error:&error];
NSString *Intersection;
NSString *currentDay;
for (NSManagedObject *obj in matchingData) {
Intersection = sendIntersection;
currentDay = sendDate;
}
NSString* fileName = [self getPDFFileName];
[PDFRenderer drawPDF:fileName intersectionSearch:Intersection dateSearch:currentDay];
[self showPDFFile];
self.navigationItem.title = #"Report";
}
So I'm trying to pass the NSManagedObjectContext of the selected row to then load. I am really lost after that. I'm not sure if passing the managed object context is right and if it is I don't know what is wrong with the code in the ReportPDFViewController. I have looked through all the tutorials I can find. I have a limited programming background so any help would be greatly appreciated.
reviewViewController.userInfo = [[self.fetchedResultsController fetchedObjects] objectAtIndex:indexPath.row];
This sets userInfo to an object of type NSManagedObject (or a subclass).
NSArray *matchingData = [userInfo executeFetchRequest:request error:&error];
This is using userInfo as if it's a NSManagedObjectContext. I would imagine you get an invalid selector error here.
What is the actual type of the userInfo attribute? It should be NSManagedObject.
You do not need to do a fetch request in your viewDidLoad. Core Data is not a database. You do not always need to do a fetch request every time you want some information. Once you already have a managed object, you can get information related to it without a fetch request. If you've set up a custom class for it, you can treat it almost like it's an regular objective-C object.
for (NSManagedObject *obj in matchingData) {
Intersection = sendIntersection;
currentDay = sendDate;
}
This code just doesn't make sense. You're looping, but each time through you're assigning the same value to the variables. I don't know what that value is, since sendIntersection and sendDate are not referred to anywhere else in the code you posted. In any case you're not using the results of the fetch request at all.
I'm going to make a wild guess at what you need to do:
Intersection = [userInfo valueForKey:#"intersection"];
currentDay = [userInfo valueForKey:#"date"];
It's a total guess, because I don't know what your data model is. No loop is needed, since you only want and have one user object.
My Data Model is named "Person" and it has 3 attributes "id", "firstName", and "lastName"
When importing the JSON data using AFNetworking I want to be able to check whether the entity already exists or not within Core Data using the "id" as the identifier. If it isn't there I would like to create it, and if it is there I would like to merge the item or update it.
right now I have a method called duplicateCheck which looks like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id==%#", _person.id];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSError *error = nil;
[fetch setEntity:[NSEntityDescription entityForName:#"Person" inManagedObjectContext:self.managedObjectContext]];
[fetch setPredicate:predicate];
NSArray *items = [self.managedObjectContext executeFetchRequest:fetch error:&error];
for (NSManagedObject *object in items) {
// Not sure how to check from here and insert or update
// then save and call it during the API request?
}
I have a predicate set up but am not sure where to go from here. Is looping over each item the right way to go or am I going about this the wrong way?
Usually one would expect an identifier to be unique. therefor if the predicate return 0 objects, you know that this object is new. If 1 is returned you know that this object already exists and maybe you need to update it.
NSArray *items = [self.managedObjectContext executeFetchRequest:fetch error:&error];
if(items){
if([items count] == 0){
//the object is not present yet. create it.
} else if([items count] == 1) {
NSManageObject *obj = items[0];
//there is exactly 1 object. change it properties if needed
} else {
//what to do if several objects have the same identifier???
}
} else {
//handle error from the error object
}
So I commented with a link to a tutorial I wrote on this topic, but to narrow it down, this method may help guide you.
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
//
// Iterate over all registered classes to sync
//
for (NSString *className in self.registeredClassesToSync) {
if (![self initialSyncComplete]) { // import all downloaded data to Core Data for initial sync
//
// If this is the initial sync then the logic is pretty simple, you will fetch the JSON data from disk
// for the class of the current iteration and create new NSManagedObjects for each record
//
NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className];
NSArray *records = [JSONDictionary objectForKey:#"results"];
for (NSDictionary *record in records) {
[self newManagedObjectWithClassName:className forRecord:record];
}
} else {
//
// Otherwise you need to do some more logic to determine if the record is new or has been updated.
// First get the downloaded records from the JSON response, verify there is at least one object in
// the data, and then fetch all records stored in Core Data whose objectId matches those from the JSON response.
//
NSArray *downloadedRecords = [self JSONDataRecordsForClass:className sortedByKey:#"objectId"];
if ([downloadedRecords lastObject]) {
//
// Now you have a set of objects from the remote service and all of the matching objects
// (based on objectId) from your Core Data store. Iterate over all of the downloaded records
// from the remote service.
//
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
int currentIndex = 0;
//
// If the number of records in your Core Data store is less than the currentIndex, you know that
// you have a potential match between the downloaded records and stored records because you sorted
// both lists by objectId, this means that an update has come in from the remote service
//
for (NSDictionary *record in downloadedRecords) {
NSManagedObject *storedManagedObject = nil;
if ([storedRecords count] > currentIndex) {
//
// Do a quick spot check to validate the objectIds in fact do match, if they do update the stored
// object with the values received from the remote service
//
storedManagedObject = [storedRecords objectAtIndex:currentIndex];
}
if ([[storedManagedObject valueForKey:#"objectId"] isEqualToString:[record valueForKey:#"objectId"]]) {
//
// Otherwise you have a new object coming in from your remote service so create a new
// NSManagedObject to represent this remote object locally
//
[self updateManagedObject:[storedRecords objectAtIndex:currentIndex] withRecord:record];
} else {
[self newManagedObjectWithClassName:className forRecord:record];
}
currentIndex++;
}
}
}
//
// Once all NSManagedObjects are created in your context you can save the context to persist the objects
// to your persistent store. In this case though you used an NSManagedObjectContext who has a parent context
// so all changes will be pushed to the parent context
//
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Unable to save context for class %#", className);
}
}];
//
// You are now done with the downloaded JSON responses so you can delete them to clean up after yourself,
// then call your -executeSyncCompletedOperations to save off your master context and set the
// syncInProgress flag to NO
//
[self deleteJSONDataRecordsForClassWithName:className];
[self executeSyncCompletedOperations];
}
}