Core Data Relationship - iphone

I have the following files:
ProductViewController.h
ProductViewController.m
ComponentsViewController.h
ComponentsViewController.m
Inside ProductViewController.m I have my button where I save the product's info to my sqlite database by using core data. Inside ComponentsViewController.m I have my button to save the Components' info to my sqlite.
I have a one-to-many (product-to-components) relationship created in my datamodel. My problem is when I save my components info instead of assigning the components to my already created product the info gets saved to an "empty" product that gets created when I press the save components button. below my code:
ProductViewController.m
- (IBAction)saveProductButton:(id)sender {
// Add product info to Data base
Product *newProduct = [NSEntityDescription insertNewObjectForEntityForName:#"Product" inManagedObjectContext:self.managedObjectContext];
newProduct.prdname = self.name.text;
newProduct.prdtdesc = self.prdtdesc.text;
newProduct.prdtid = self.prdtid.text;
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
// 4
self.name.text = #"";
self.prdtid.text = #"";
self.prdtdesc.text = #"";
// 5
[self.view endEditing:YES];
}
ComponentsViewController.m
- (IBAction)addComponent:(id)sender {
Product *newProduct = [NSEntityDescription insertNewObjectForEntityForName:#"Product" inManagedObjectContext:self.managedObjectContext];
Components * newComponent = [NSEntityDescription insertNewObjectForEntityForName:#"Components"
inManagedObjectContext:self.managedObjectContext];
newComponent.prdtcompname = self.prdtcompname.text;
newComponent.prdtcompid = self.prdtcompid.text;
newComponent.prdtcompdesc = self.prdtcompdesc.text;
newComponent.prdtcompqt = self.prdtcompQT.text;
newProduct.tocomponents = [NSSet setWithObjects:newComponent, nil];
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
// 4
self.prdtcompQT.text = #"";
self.prdtcompname.text = #"";
self.prdtcompid.text = #"";
self.prdtcompdesc.text = #"";
// 5
[self.view endEditing:YES];
}

Dont Create a new product while creating a component. While creating a component, just fetch a product with the relative ID and store the product reference in component.
Here is my sample code:
I have created a same kind of one to many relationship (Item -> Item_Details)
Step 1: First i do insertion in item entity.
Step 2 : while Insertion of item_Details i just fetch item.info from the Item entity using the relative Id.
Once you create a NSManagedObjectSubClass (with relationship) it will automatically create the reference instance as u mentioned.
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class Item;
#interface Item_Detail : NSManagedObject
#property (nonatomic, retain) NSNumber * item_Id; // Item_Id is a common id in both item and Item_details should act as a relation.
#property (nonatomic, retain) NSString * item_Size;
#property (nonatomic, retain) NSString * last_Modified;
#property (nonatomic, retain) NSNumber * price;
#property (nonatomic, retain) Item *itemInfo; // RelationShip reference
#end
// Fetching the Item info with relationship ID:
+(Item *)getSourceItemDetail:(NSNumber *)itemId
{
NSEntityDescription *itemEntity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:[ASDataManager managedObjectContext]];
NSFetchRequest *itemFetchRqt= [[NSFetchRequest alloc]init];
[itemFetchRqt setEntity:itemEntity];
NSPredicate *itempredicate = [NSPredicate predicateWithFormat:#"item_Id = %#",itemId];
[itemFetchRqt setPredicate:itempredicate];
NSError *error = nil;
NSArray *resultArray = [[ASDataManager managedObjectContext] executeFetchRequest:itemFetchRqt error:&error];
if([resultArray count] == 1)
{
Item *item = (Item *)[resultArray objectAtIndex:0];
return item;
}
return nil;
}
//Item_Detail _Insertion
+(void)insertNewItemDetailRecordInItemDetailTable:(NSDictionary *)itemDetailDic
{
Item_Detail *item_Dtls = (Item_Detail *)[NSEntityDescription insertNewObjectForEntityForName:#"Item_Detail" inManagedObjectContext:[ASDataManager managedObjectContext]];
item_Dtls.item_Id = [NSNumber numberWithInt:[[itemDetailDic valueForKey:#"Item_ID"] intValue]];
item_Dtls.item_Size = [ASGlobalInfoHelper getValueIfNotNullFrom:[itemDetailDic objectForKey:#"Item_Size"] valueType:ValueTypeString];
item_Dtls.itemInfo = [self getSourceItemDetail:[NSNumber numberWithInt:#"90"]];
//Getting Relationship data
if(![[ASDataManager managedObjectContext] save:nil])
NSLog(#"Error in Save");
}

In ComponentsViewController.m, you are creating an empty product here in this line:
Product *newProduct = [NSEntityDescription insertNewObjectForEntityForName:#"Product" inManagedObjectContext:self.managedObjectContext];
If you want to have a relationship with newly created Components object with the previously created Product object, you either have to fetch the Product from Core Data using FetchRequest or pass the Product from ProductViewController to ComponentsViewController.

Related

NSArray is null when i create it in the completion whereas the first nsarray is not null

This is my code trying to create NSMutable array with strings and then to store them in the object property. NSArray *photos works but not the NSMUtableArray thumbImageURL. When i NSLog it for debugging purposes it is null. Please help, it annoys me a lot, cant find a solution.
i also lazy instantiated so there is no reason it will not have allocation in the memory.
Lazy Instantiation:
-(void)setThumbImageURL:(NSMutableArray *)thumbImageURL
{
if (!_thumbImageURL) _thumbImageURL=[[NSMutableArray alloc] initWithCapacity:50];
_thumbImageURL=thumbImageURL;
}
My code:
[PXRequest requestForPhotoFeature:PXAPIHelperPhotoFeaturePopular resultsPerPage:50 page:1 photoSizes:(PXPhotoModelSizeLarge | PXPhotoModelSizeThumbnail | PXPhotoModelSizeSmallThumbnail |PXPhotoModelSizeExtraLarge) sortOrder:PXAPIHelperSortOrderCreatedAt completion:^(NSDictionary *results, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
if (results) {
self.photos =[results valueForKey:#"photos"];
NSLog(#"%#",self.photos);
}
NSLog(#"\n\n\n\n\n\n\n\nSelf photos count : %lu",[self.photos count]);
for (int i=0; i<[self.photos count]; i++)
{
NSURL *thumbImageUrl= [NSURL URLWithString:[[[self.photos valueForKey:#"images"] [i] valueForKey:#"url"] firstObject]];
NSData *imageData=[NSData dataWithContentsOfURL:thumbImageUrl];
[self.thumbImageURL addObject:imageData];
self.largeImageURL[i]=[[[self.photos valueForKey:#"images"] [i] valueForKey:#"url"] lastObject];
}
NSLog(#"\n\n\n\n\n\n\n\nSelf Thum Image after : %#",self.thumbImageURL);
NSLog(#"\n\n\n\n\n\n\n\nSelf large Image after : %#n\n\n\n\n\n\n\n",self.largeImageURL);
}];
I myself found the problem. The problem is that lazy instantiation is on the setter whereas it should be on the getter
Change the Lazy Instantiation:
From:
-(void)setThumbImageURL:(NSMutableArray *)thumbImageURL
{
if (!_thumbImageURL) _thumbImageURL=[[NSMutableArray alloc] initWithCapacity:50];
_thumbImageURL=thumbImageURL;
}
To:
#property (nonatomic, strong) NSMutableArray *thumbImageURL;
/**
* lazy load _thumbImageURL
*
* #return NSMutableArray
*/
- (NSMutableArray *)thumbImageURL
{
if (_thumbImageURL == nil) {
_thumbImageURL = [[NSMutableArray alloc] initWithCapacity:50];
}
return _thumbImageURL;
}

Converting ManagedObject to NSObject

I am trying to get the data from 'Employee' Entity which has empId, Name, deptId as attributes.
// Used to Populate data in table
Employee : NSObject
#property (nonatomic, retain) NSString *name;
#property int empId;
#property int deptId;
#synthesize empId, name, deptId;
&
CDEmployee : NSManagedObject
#property (nonatomic, retain) NSString *name;
#property int empId;
#property int deptId;
- (void)convertMyData:(Employee *)emp;
#dynamic empId, name, deptId;
- (void)convertMyData:(Employee *)emp
{
self.empId = emp.empId;
self.name = emp.name;
self.deptId = emp.deptId;
}
// My code to fetch & convert data retured from db to Employee class
-(NSArray *)getAllEmployees:(NSManagedObjectContext*)context
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(empId > %#)",[NSNumber numberWithInt:-1],[NSNumber numberWithInt:1]];
NSFetchRequest* req = [self createRequest:context]; // Request is correct
[req setPredicate:predicate];
NSFetchedResultsController* fetchContr = [[NSFetchedResultsController alloc] initWithFetchRequest:req managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
NSError* error = nil;
[fetchContr performFetch:&error];
NSArray *fetchedObjects = [fetchContr fetchedObjects];
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
for(int i = 0; i < [fetchedObjects count]; i++)
{
CDEmployee *cdEmp = [[fetchedObjects objectAtIndex:i] retain]; // just tried retaing
if (cdEmp)
{
Employee *emp = [[Employee alloc] init];
NSLog(#"Employee - %#", emp); // Shows tht is has object
[cdEmp convertMyData:emp]; //-----> Crashing here
[result addObject:emp];
[emp release];
}
}
[fetchContr release];
return result;
}
I am fetching results from core data & I am getting correct results back, but when I convert my core data result back to Employee(NSObject) class i am getting [NSManagedObject convertMyData:]: unrecognized selector sent to instance.
I tried adding another method say -(void)helloWorld to CDEmployee class & tried [cdEmp helloWorld]; but got the same crash.
Not getting why its causing the problem. I have method defined & implemented at proper place & its not even giving warning to me at compile time.
Here is a sample code that converts NSManagedObject to NSDictionary.
NSDictionary is even more easier to handle and operate on the data than NSObject. Also refer this Git Library for other similar iOS utility class methods.
+(NSDictionary *)convertManagedObjectToDictionary:(NSManagedObject *)managedObject{
if (!managedObject){
NSLog(#"Managed object is nil");
return nil;
}
else{
unsigned int objectsCount;
objc_property_t *objectProperties = class_copyPropertyList([managedObject class], &objectsCount);
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
for(int i = 0; i < objectsCount; i++) {
objc_property_t property = objectProperties[i];
NSString *name = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
id object = [managedObject valueForKey:name];
[dictionary setObject:((object == nil) ? [NSNull null] : object) forKey:name];
}
free(objectProperties);
return dictionary;
}
}
I tried in different way & got resolved http://www.raywenderlich.com/934/core-data-tutorial-for-ios-getting-started
First, you should really first think about what you want to accomplish from a non-technical perspective. What is it, actually?
Second, NSManagedObject is a subclass of NSObject, which means you already have a NSObject. Congratulations, you are done!
Also, in your code you call fetchMyData but did not show us where you define it or what the code looks like. Maybe a NSManagedObject category would be in order here? But still, this begs the question why you would want to do this. Even if you were to convert into a Foundation object, such as NSDictionary as suggested in the other answer, you would still have a problem with modelling relationships...
Finally, what SDK are you using? You can't be serious if you are still not using ARC.

Error: NSArray was mutated while being enumerated thrown when accessing globals and Core Data

I have this piece of code which I am using to update some values in Core Data when another object is added:
//Create new receipt
Receipt *receipt = [[Receipt alloc] init];
receipt.project = self.projectLabel.text;
receipt.amount = self.amountTextField.text;
receipt.descriptionNote = self.descriptionTextField.text;
receipt.business = self.businessNameTextField.text;
receipt.date = self.dateLabel.text;
receipt.category = self.categoryLabel.text;
receipt.paidBy = self.paidByLabel.text;
receipt.receiptImage1 = self.receiptImage1;
//Need to set this to 2
receipt.receiptImage2 = self.receiptImage1;
receipt.receiptNumber = #"99";
int count = 0;
int catCount = 0;
for (Project *p in appDelegate.projects)
{
if ([p.projectName isEqualToString:receipt.project]){
double tempValue = [p.totalValue doubleValue];
tempValue += [receipt.amount doubleValue];
NSString *newTotalValue = [NSString stringWithFormat:#"%.02f", tempValue];
NSString *newProjectName = p.projectName;
//remove entity from Core Data
NSFetchRequest * allProjects = [[NSFetchRequest alloc] init];
[allProjects setEntity:[NSEntityDescription entityForName:#"Project" inManagedObjectContext:appDelegate.managedObjectContext]];
[allProjects setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * projectsArray = [appDelegate.managedObjectContext executeFetchRequest:allProjects error:&error];
//Delete product from Core Data
[appDelegate.managedObjectContext deleteObject:[projectsArray objectAtIndex:count]];
NSError *saveError = nil;
[appDelegate.managedObjectContext save:&saveError];
[appDelegate.projects removeObjectAtIndex:count];
NSLog(#"Removed project from Core Data");
//Insert a new object of type ProductInfo into Core Data
NSManagedObject *projectInfo = [NSEntityDescription
insertNewObjectForEntityForName:#"Project"
inManagedObjectContext:appDelegate.managedObjectContext];
//Set receipt entities values
[projectInfo setValue:newProjectName forKey:#"name"];
[projectInfo setValue:newTotalValue forKey:#"totalValue"];
if (![appDelegate.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Added Project to Core Data");
Project *tempProject = [[Project alloc] init];
tempProject.projectName = [projectInfo valueForKey:#"name"];
tempProject.totalValue = [projectInfo valueForKey:#"totalValue"];
[appDelegate.projects addObject:tempProject];
}
count++;
}
for (Category *c in appDelegate.categories){
if ([c.categoryName isEqualToString:receipt.category]){
double tempValue = [c.totalValue doubleValue];
tempValue += [receipt.amount doubleValue];
NSString *newTotalValue = [NSString stringWithFormat:#"%.02f", tempValue];
NSString *newCategoryName = c.categoryName;
//remove entity from Core Data
NSFetchRequest * allCategories = [[NSFetchRequest alloc] init];
[allCategories setEntity:[NSEntityDescription entityForName:#"Category" inManagedObjectContext:appDelegate.managedObjectContext]];
[allCategories setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * categoriesError = nil;
NSArray * categoriesArray = [appDelegate.managedObjectContext executeFetchRequest:allCategories error:&categoriesError];
//Delete product from Core Data
[appDelegate.managedObjectContext deleteObject:[categoriesArray objectAtIndex:catCount]];
NSError *categorySaveError = nil;
[appDelegate.managedObjectContext save:&categorySaveError];
[appDelegate.categories removeObjectAtIndex:catCount];
NSLog(#"Removed category from Core Data");
NSError * error = nil;
//Insert a new object of type ProductInfo into Core Data
NSManagedObject *categoryInfo = [NSEntityDescription
insertNewObjectForEntityForName:#"Category"
inManagedObjectContext:appDelegate.managedObjectContext];
//Set receipt entities values
[categoryInfo setValue:newCategoryName forKey:#"name"];
[categoryInfo setValue:newTotalValue forKey:#"totalValue"];
if (![appDelegate.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Added Category to Core Data");
Category *tempCategory = [[Category alloc] init];
tempCategory.categoryName = [categoryInfo valueForKey:#"name"];
tempCategory.totalValue = [categoryInfo valueForKey:#"totalValue"];
[appDelegate.categories addObject:tempCategory];
}
catCount++;
}
This code gives the error:
'...was mutated while being enumerated'.
Can anyone explain why? Also, is there a better approach to doing what I'm trying to achieve?
The error you're seeing is accurate. The problem is that you're mutating (changing) a collection while iterating over it. Basically, you're doing something of the form:
for (Project *p in appDelegate.projects) {
...
[p addObject: X]
}
This isn't allowed.
One simple solution is to make a new collection of the objects you want to add, and then add them to the original container outside of your loop. Something like:
NSMutableArray *array = [NSMutableArray array];
for (Project *p in appDelegate.projects) {
...
[array addObject:X];
}
[p addObjects:array];
By the way, did you google for the error text "was mutated while being enumerated"? I'd be surprised if you didn't find the answer for this common problem just by googling.
Also, when posting an error message, it's helpful to post the full line, not just part of it.
You are adding and removing items from appDelegate.projects while iterating over it in a for-each loop here:
[appDelegate.projects removeObjectAtIndex:count];
// ...
[appDelegate.projects addObject:tempProject];
And the same for appDelegate.categories:
[appDelegate.categories removeObjectAtIndex:count];
// ...
[appDelegate.categories addObject:tempProject];
AFAIK, in such case you should use a simple for loop, and access the array with index.

Core Data - How to Insert an Array

I'd like to know how to save several string objects, all stored in an array, using Core Data.
I understand how to store a single string, but is there any convenience method/can I store the array object itself rather than iterating over the array and storing each string item seperately?
NSManagedObject *alice = [NSEntityDescription insertNewObjectForEntityForName:#"Student" inManagedObjectContext:context];
[alice setValue:#"Alice" forKey:#"name"];
[alice setValue:#"Computer Science" forKey:#"major"];
Basically, can I have setValue be an array instead of Alice?
(As an unrelated question how can I cache data on the iphone, such as an image...)
Thanks for any help
Look at this post : insert NSDictionary into CoreData just replace the word NSDictionary by NSArray apart from that it's the same question.
I don't know if there are any prebuilt methods. You'd have to track keys in a separate container, for example, or define them as constants somewhere, such as in the following example:
static NSUInteger const kMyNameIdx = 0U;
static NSUInteger const kMyMajorIdx = 1U;
static NSString * const kMyNameKey = #"name";
static NSString * const kMyMajorKey = #"major";
/* this does no error checking on the mo or array */
/* being null. it would be better to return an */
/* NSError from this function and check its value */
/* to handle error cases */
- (void) updateManagedObject:(NSManagedObject *)mo withOrderedArray:(NSArray *)array
{
id obj;
NSUInteger objIdx = 0U;
/* this assumes that name and major objects in */
/* your array are in the same order as set by */
/* the constants */
for (obj in array) {
switch (objIdx) {
case kMyNameIdx:
[mo setValue:obj forKey:kMyNameKey];
break;
case kMyMajorIdx:
[mo setValue:obj forKey:kMyMajorKey];
break;
default:
break;
}
objIdx++;
}
}
To use it:
NSManagedObject *alice = [NSEntityDescription insertNewObjectForEntityForName:#"Student" inManagedObjectContext:context];
NSArray *myArray = /* ... */
[self updateManagedObject:alice withOrderedArray:myArray];
There are additional modifications you can make, such as making this a category method for NSManagedObject of entity type Student. Then you can call this function anywhere you use Student managed objects.
where ever you called this method with your array
-(void)viewDidLoad
{
[self insertLoginData:YOUR ARRAY NAME];
}
- (BOOL)insertLoginData:(NSMutableArray *)loginInfoArray
{
NSError *error=nil;
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *propertyInfo = [NSEntityDescription
insertNewObjectForEntityForName:#"UserLogin"
inManagedObjectContext:context];
for(int count=0;count<[loginInfoArray count];count++)
{
[propertyInfo setValue:[[loginInfoArray objectAtIndex:count]objectForKey:#"UserId"] forKey:#"userName"];
[propertyInfo setValue:[[loginInfoArray objectAtIndex:count]objectForKey:#"Password"] forKey:#"password"];
}
if (![__managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
return NO;
}
else
{
return YES;
}
}
Fetch data ==========
-(NSMutableArray *)fetchLoginData
{
NSFetchRequest *fetchReq = [[NSFetchRequest alloc]init];
[fetchReq setEntity:[NSEntityDescription entityForName:#"UserLogin" inManagedObjectContext:self.managedObjectContext]];
NSMutableArray *resultArray = [[NSMutableArray alloc]initWithArray:[self.managedObjectContext executeFetchRequest:fetchReq error:nil]];
NSMutableArray *array=[[NSMutableArray alloc]init];
for(UserLogin *pnt in resultArray)
{
//[array addObject:pnt.userName];
[array addObject:pnt];
}
return array;
}

How can I modify NSMutableArray property?

I use 2 sqlite db files.
1) fill items from A sqlite db.
#property (nonatomic, strong) NSMutableArray *items;
.......
FMResultSet *results = [db1 executeQuery:query];
while ([results next]) {
Book *book = [[Book alloc] init];
book.content = [results stringForColumn:#"Zcontent"];
book.title = [results stringForColumn:#"Ztitle"];
book.idx = [results intForColumn:#"Zidx"];
book.highlight_YN = NO;
[items addObject:book];
[book release];
}
2) After then, select items from other -B- sqlite db.
FMDatabase *userDB = [FMDatabase databaseWithPath:userfmdbPath];
NSString *query2 = [NSString stringWithFormat:#"SELECT zidx, highlight_YN FROM zHighlight where zbook = %d and zchapter = %d order by zidx ", pBook,pChapter];
FMResultSet *userresults = [userDB executeQuery:query2];
3) Question : I want to modify property(highlight_YN) of items with results of userresults compared with results - zidx .
How can I modify NSMutableArray property?
You can loop through the items array and search for the object.
for ( Book *book in items ) {
if( book.idx == [userresults intForColumn:#"zidx"] )
{
book.highlight_YN = [userresults boolForColumn:#"highlight_YN"];
break;
}
}