fetch coredata relationship - ios

I would like to know how to fetch the items from my coredata relation ship. I guess it should be a dictionary or arrays or something that gets returned so that you can have your one to many thing.
I am quite lost at this I know how to write/read single objects but this relationship stuff is abit confusing.
I think I have sucsessfully written a relationship to coredata however now I would like to be able to read it to see if I have it right.. I have started writing the method for this but have no idea what to actually do to get all of the information out.
this is the code i have so far.. for both read and write
- (void)writeFin:(NSArray *)recivedProjectData ItemsData:(NSArray *)itemsData {
// WRITE TO CORE DATA
NSManagedObjectContext *context = [self managedObjectContext];
for (NSDictionary *dict in recivedProjectData) {
project = [NSEntityDescription insertNewObjectForEntityForName:#"Project" inManagedObjectContext:context];
project.proNumber = [dict valueForKey:#"ProNumber"];
project.projectDescription = [dict valueForKey:#"Description"];
// project.items = [dict valueForKey:#""]; // this is the relationship for project
}
for (NSDictionary *dict in itemsData) {
items = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:context];
items.Number = [dict valueForKey:#"Number"];
items.Description = [dict valueForKey:#"Description"];
items.comment = [dict valueForKey:#"Comment"];
items.project = project; // this is the relationship for items
[project addItemsObject:items];
}
NSError *error = nil;
if (![__managedObjectContext save:&error]) {
NSLog(#"There was an error! %#", error);
}
else {
NSLog(#"created");
}
}
- (NSMutableArray *)readFin {
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *projectDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (ProjectList *projectList in fetchedObjects) {
NSMutableDictionary *tempProjectDictionaryArray = [[ NSMutableDictionary alloc] init];
[tempProjectDictionaryArray setObject:project.proNumber forKey:#"ProNumber"];
[tempProjectDictionaryArray setObject:project.description forKey:#"Description"];
[tempProjectDictionaryArray setObject:project.projectID forKey:#"ProjectID"];
[projectDictionaryArray addObject:tempProjectDictionaryArray];
}
return projectDictionaryArray;
}
So just o reiterate, I would like to know A, is my write method look okay? B, how do you fetch/read the relationship objects from core data.
any help would be greatly appreciated.

A relationship in Core Data isn't an object, its a property which happens to correspond to another object in your model rather than just being a dead end. You're already most of the way there as far as checking whether your relationships are ok as far as I can see -- what you need to do is add one more line in your projectList
[tempProjectDictionaryArray setObject: project.items forKey:#"items"];
the object that you will have added will be an NSSet. You can then check that things are as they should be with a loop like this after you've finished setting things up
NSSet itemsForProject = projectDictionaryArray[someIndex][#"items"]
for (Item* currItem in [itemsForProject allObjects]) {
//access some property of the current item to make sure you have the right ones -- \
description for example
NSLog(#"%#", item.description);
}

Related

Populating an Array from Core Data in Xcode

I am trying to populate an NSArray with a collection of data I get from CoreData. But my array doesnt seem to be populating with the data. I have the following code to retrieve the data:
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]
initWithEntityName:#"WeightLog"];
self.contactarray = [[managedObjectContext executeFetchRequest:fetchRequest
error:nil] mutableCopy];
And I am using the following for loop to populate the NSArray with the data I collect from WeightLog for a particular field.
for (int i =0; i<=self.contactarray.count; i++) {
NSManagedObject *device = [self.contactarray objectAtIndex:i];
[titleNames addObject:device];
}
Just so you know contactarray is a property in my .h file of the following format:
#property (strong) NSMutableArray *contactarray;
Can you tell me where I am going wrong please, I am fairly new to iOS Development, if it doesn't show.
Thank you in advance
Initialise titleNames array before use. Try this,
titleNames = [[NSMutableArray alloc] init];
for (int i =0; i<=self.contactarray.count; i++) {
NSManagedObject *device = [self.contactarray objectAtIndex:i];
[titleNames addObject:device];
}
Just call this user-defined method. for ex -
self.titleNames = [self selectAllRowInEntity:#"WeightLog"];
-(NSArray *) selectAllRowInEntity:(NSString *) entityName
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fRequest;
NSEntityDescription *eDesc;
NSArray *arr;
fRequest = [[NSFetchRequest alloc] init];
eDesc = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fRequest setEntity:eDesc];
arr = [managedObjectContext executeFetchRequest:fRequest error:nil];
return arr;
}
This line here:
self.contactarray = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
This is a cardinal sin in Core Data - to not use the provided error parameters.
NSError *error;
self.contactarray = [[managedObjectContext executeFetchRequest:fetchRequest
&error] mutableCopy];
if (!self.contactArray) {
// Fetch Requests return a nil on error, in which case you should check the error.
NSLog(#"Error occurred: %#", error);
} else {
// do whatever you want with the array
}
Now run your code and look at the console and you might see the reason for the error.
Edited to add
Following a comment:
You should always check that the return of the method is nil before evaluating the error object. For Cocoa (and Cocoa-Touch) methods this is the only time that the error parameter is guaranteed to be valid.
This is taken from the Error Handling Programming Guide

Edit CoreData object then save context

I have two entities, one called InProject that has several attributes and one relationship. the relationship is with another entity called Ins.
I am editing one of the Ins that is related to InProject. I used InProject attribute ID which then returns a NSDictionary value that has several key-values one of which is for an array of Ins. I then find the Ins I need to edit in a for loop I edit them, but then I become unstuck because I am not sure how to save the contect of InProject with the *updated Ins
I need to figure out how to save InProject after I have overwritten the Ins attributes I need to update.
This is what my code looks like after battling this problem:
- (void)editSelectedins:(NSString *)projIDString UpdatedNSD:(NSMutableDictionary *)updatedNSD DPC:(int)dpc{
// get context
NSManagedObjectContext *context = [self managedObjectContext];
if (context == nil) {
NSLog(#"Nil");
}
else {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InsProject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *InsProjectDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (InsProject *insProj in fetchedObjects) {
NSMutableDictionary *tempInsProjectDictionaryArray = [[ NSMutableDictionary alloc] init];
[tempInsProjectDictionaryArray setObject:insProj.companyName forKey:#"CompanyName"];
[tempInsProjectDictionaryArray setObject:insProj.projNo forKey:#"ProjNo"];
[tempInsProjectDictionaryArray setObject:insProj.desc forKey:#"Desc"];
[tempInsProjectDictionaryArray setObject:insProj.guid forKey:#"GUID"];
[tempInsProjectDictionaryArray setObject:insProj.projID forKey:#"ProjID"];
[tempInsProjectDictionaryArray setObject:insProj.ins forKey:#"ins"];
[InsProjectDictionaryArray addObject:tempInsProjectDictionaryArray];
}
// now that you have the InsProjects, choose the one you are curently working on in insView using the projectID
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ProjID==%#",projIDString];
[fetchRequest setPredicate:predicate];
// new array with one value that was created using the NSPredicate ProjID
NSArray *tempInsProjectArray = [InsProjectDictionaryArray filteredArrayUsingPredicate:predicate];
// get ins array out of the NSDictionary to edit
NSSet *inssForInsProject = tempInsProjectArray[0][#"ins"];
NSMutableArray *tempAllinss = [[NSMutableArray alloc] init]; // this will contain everything, that means all repeated values are included
for (Items* currItem in [inssForInsProject allObjects]) {
NSArray *keys = [[[currItem entity] attributesByName] allKeys];
NSDictionary *dict = [currItem dictionaryWithValuesForKeys:keys];
[tempAllinss addObject:dict];
}
NSArray *myArray = [tempAllinss copy];
// get the correct items from myArray anything whos dpc matches the dpc parameter of this method
NSMutableArray *editedinsArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [myArray count]; i++) {
NSMutableDictionary *tempinssDictionary = [myArray objectAtIndex:i];
// if you get a match put it into the new editedinsArray to be edited
if ([[tempinssDictionary objectForKey:#"dpc"] integerValue] == dpc) {
[editedinsArray addObject:tempinssDictionary];
}
}
// by now you should have three things
// 1, access to your ins coredata object //this s wrong I actually have access to insProject
// 2, the values you need to be edited saved into a NSArray (editedinsArray, which will be used to check against and keep old values correct)
// 3, UpdatedNSD which will be used to update any values that need to be updated.
// go through your values and update the ins object
int i = 0;
for (ins *temp in editedinsArray) {
NSDictionary *currentEditedins = [editedinsArray objectAtIndex:i];
i++;
// these values should stay the same so use currentEditedins which contains old vals
NSString *stringToNumberDpc = [currentEditedins valueForKey:#"dpc"];
int tempDpcNum = [stringToNumberDpc integerValue];
NSNumber *dpcNumber = [NSNumber numberWithInt:tempDpcNum];
temp.dpc = dpcNumber;
NSString *totDQtyString = [currentEditedins valueForKey:#"totDQty"];
if ((NSNull *)totDQtyString == [NSNull null]) {
temp.totDQty = #"";
} else {
temp.totDQty = totDQtyString;
}
NSString *totShipString = [currentEditedins valueForKey:#"totShip"];
if ((NSNull *)totShipString == [NSNull null]) {
temp.totShip = #"";
} else {
temp.totShip = totShipString;
}
// values to be updated so use updatedNSD wthich was passed in as method param with the new vals
temp.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
temp.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
temp.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
#warning --- I have no idea what to do here... i.e. how do I update the tempInsProjectArray.ins values I have just updated in the above for loop then save context which I hope would update insProj and the ins entities involved.
//save
[context save:&error];
}
}
As you can see at the bottom of the code with #warning I explain where I am having the issue. if I log temp inside the for loop I see the updated values perfectly the issue I am having is how do I then update the current tempInsProjectArray.ins values that I have just edited? then save them of course.
Your code is in great need of simplification. Some ground rules:
Use names with smallInitial and camelCase for variables. So not InsProjectDictionaryArray but insProjectDictionaryArray.
The same applies to dictionary keys indicating attribute names of managed objects. So projNo, not ProjNo.
Avoid cryptic abbreviations. Use plain and readable English Not projNo but projectNumber. What is an Ins? What is "dcp"?
Don't use the plural form for entity names. An suitable name for an item is Item, not Items
Don't use the mutable versions of dictionary and array when immutable ones would do.
Avoid duplicating your data, such as in [array copy].
Avoid dictionaries when you have an object graph. The object graph is what core data creates. It renders dictionaries with values and keys unnecessary.
Don't use IDs. The object graph renders those unnecessary as well in most cases. If you use IDs, do not use strings but numbers, such as long ints, or the object version NSNumber.
When fetching data from the Core Data persistent store, don't fetch all the data and the filter the result. Fetch only the data you need.
What you want to accomplish can surely be done in a few lines of code. I will try to summarize what you want to do as far as I understand it.
Your data model looks something like this:
Project <----->> Item
Where the items are in a to-many relationship called ins. I will rename this items. I will also assume that you will refactor your IDs to be of type NSNumber.
All the code up to myArray could be substituted with this:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:"Project"];
request.predicate = [NSPredicate predicateWithFormat:#"projectID = %#", projectID];
request.fetchLimit = 1;
NSArray *fetchedObjects = [self.managedObjectContext
executeFetchRequest:request error:nil];
Project *project = fetchedObjects[0];
You now have all items available simply with project.items. I understand that there could be more than one item with a mysterious attribute dcp of type int (i.e. NSNumber for managed objects), that is equal to the dcp parameter passed.
NSSet *matchingItems = [project.items filteredSetUsingPredicate:
[NSPredicate predicateWithFormat:#"dcp = %#", #(dcp)]];
Now it becomes a bit murky. Why do you have type ins in your for loop if the ins are actually of type Item? You then cast them into a dictionary... This should generate a compiler error. Or you have another class called ins instead of Ins??
Anyway, if you stay with the Items you can just update the values with what you pass in your dictionary:
for (Item *item in matchingItems) {
item.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
item.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
item.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
[self.managedObjectContext save:nil];
Done!
BTW you could make it even shorter by setting the entity name of the fetch request to "Item" and setting the following predicate:
[NSPredicate predicateWithFormat:#"project.projectID = %# && dcp = %#",
projectID, #(dcp)];
If you know your InProject, then updating your Ins related to that project is a matter of editing property values on your managed objects.
Why not use the predicate to get an NSManagedObject of the InProject, then pull the relationship off of that and edit the values?
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
return;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InsProject" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Set the predicate on the Core Data fetch request instead
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"ProjID==%#",projIDString];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
// We now have an array that has objects matching the projectIdString
// Might want to do some additional checks if you're only expecting zero or one objects
InsProject *aProject = [fetchedObjects lastObject];
// If we have no project, no point going any further
if ( !aProject ) return;
// On this NSManagedObject is an NSSet property with all related Ins objects
for ( Ins *anIns in aProject.ins ) {
// If our Ins item matches the passed dpc...
if ( [ins.dpc integerValue] == dpc ) {
// ...we have a match, edit properties
ins.dpc = #(dpc);
ins.newInsComp = [updatedNSD valueForKey:#"newInsComp"];
ins.newDryComp = [updatedNSD valueForKey:#"newDryComp"];
ins.updatedRow = [updatedNSD valueForKey:#"updatedRow"];
}
}
// These are managed objects, so saving the context saves all the changes
NSError *saveError;
[context save:&saveError];
if ( saveError ) {
NSLog(#"Save error: %#", [error localizedDescription]);
}

how to edit coredata value then save context to overwrite old values?

I am trying to edit a item in one of my coredata tables/entities. I am not 100% sure how to do this but I think its along these lines.
First you create the context, then fetchrequest the entity using a predicate to select the exact item from the table/entity. then save these values into the correct var type update the values then some how overwrite the existing item with the new values.
The last part is where I am stuck, this is the code I currently have:
- (void)editSelectedInstall:(NSString *)invGuid {
NSManagedObjectContext *context = [self managedObjectContext];
if (context == nil) {
NSLog(#"Nil");
}
else {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Install" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"invGUID==%#",invGuid];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *myArray = [context executeFetchRequest:fetchRequest error:&error];
NSMutableDictionary *myDictionary = [myArray objectAtIndex:0];
NSLog(#"%#", myDictionary);
// Here I will update the values & then save the context?
// For example I think I am to update the items like this:
myDictionary.num = #"1234";
myDictionary.name = #"new name";
}
}
I think I'm almost there I just need help saving the context so that it overwrites the previous values.
Try this:
//loop through result set and update as required
for (Install *temp in myArray) {
temp.num = #"1234";
temp.name = #"New name";
}
//save
[context save:&error];

Managing core data objects with a data manager singleton

I have a set of items in a plist. When my app starts, I read in the plist and save it as an array in my DataManager singleton, like this:
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *itemDatapath = [path stringByAppendingPathComponent:#"ItemData.plist"];
NSDictionary *itemData = [NSDictionary dictionaryWithContentsOfFile:itemDatapath];
dataManager.items = [itemData objectForKey:#"Items"];
I also want to store the core data objects that are associated with this data in the DataManger, so I attempted this:
-(void)setItems:(NSArray *)_items //causes EXC_BAD_ACCESS error
{
self.items = _items;
NSManagedObjectContext *context = [self managedObjectContext];
for (NSDictionary *item in self.items)
{
NSManagedObject *itemObject = [NSEntityDescription
insertNewObjectForEntityForName:#"Item"
inManagedObjectContext:context];
[itemObject setValue:[NSNumber numberWithInteger:[[item valueForKey:#"id"] intValue]] forKey:#"identifier"];
[itemObject setValue:[UIImage imageNamed:[item valueForKey:#"image"]] forKey:#"image"];
...
}
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
The point being that anywhere in my app I can access the objects from this method:
-(NSArray*)fetchItems
{
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Item" inManagedObjectContext:managedObjectContext];
NSError *error2;
NSFetchRequest *itemFetchRequest = [[NSFetchRequest alloc] init];
[itemFetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"order"
ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[itemFetchRequest setSortDescriptors:sortDescriptors];
NSArray *fetchedItems = [managedObjectContext executeFetchRequest:itemFetchRequest error:&error2];
return fetchedItems;
}
The problem is the EXC_BAD_ACCESS error noted above. I would also like to know if there is a better way of going about this. I have the feeling storing the core data objects here is not the best practice. But even if I fetch the data when I need it in other view controllers, how can I manage updating the core data objects if they change? I have an external plist that may change, and the core data objects need to update based on that.
You are causing infinite recursion when you put self.items = _items inside the setItems: method. self.items is exactly the same as calling setItems - they invoke the same method. What you need to do instead is set the value of whatever your instance variable is - presumably items. So the first line of setItems: should be items = _items. That, in and of itself, is also confusing, as the convention is to have _ before variables indicate an instance variable.

Core Data returning NSArrays instead of NSStrings

For some reason all of the NSString typed attributes are being returned as NSArrays in my Article object. Here's my function to retrieve them:- (NSArray *)getSavedArticles
{
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
NSError *error = nil;
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Article" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *dateSort = [NSSortDescriptor sortDescriptorWithKey:#"last_opened" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:dateSort]];
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
return fetchedObjects;
}
I have not created a NSManagedObjectModel for these, and instead I'm just accessing them using KVO.
//self.data has the array returned from getSavedArticles
NSManagedObject *object = [self.data objectAtIndex:indexPath.row];
NSString *path = [object valueForKey:#"path"];
NSString* id = [object valueForKey:#"id"];
When I look at this in the variables pane, on path and id I see (__NSArrayI *) ... Variable is not a CFString. Printing the description of either of these also prints out the parenthesis used when printing arrays.
I'm just wondering if this could be due to the sort descriptor? I have double checked that the data going into these objects is typed correctly, and the SQLite database that I'm using to backup everything is displaying strings.
I have tried re-installing the app in the simulator, and resetting it. I still get a NSArray instead of a NSString.
I really don't know what's going on here. Any help would be appreciated.
EDIT: I just found something else interesting. I do another check to see if an Article has been saved, and this time I don't get a NSArray for the article path:- (NSString *)hasArticleSaved:(NSString *)id
{
NSString *path = nil;
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"%K == %#", #"id", id]];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Article" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects.count > 0) {
NSManagedObject *savedArticle = [fetchedObjects objectAtIndex:0];
path = [savedArticle valueForKey:#"path"];
}
return path;
}
Now I'm really confused. Any takers?
Your problem is here:
[self.data objectForKey:indexPath.row]
...because objectForKey: is looking for a string key name e.g. "path" or "id". Giving it an integer will produce an error or a random return.
Instead, you want:
[self.data objectAtIndex:indexPath.row]
... which will return the managed object at the given index in the array.
I found the issue. I had changed the data structure for my table so self.data is an array of arrays...and with only 1 object it looked like I was getting a NSArray back when in fact I was just accessing the data wrong. I just needed to do [[self.data objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];

Resources