Fetched Results Controller objectAtIndexPath without using TableView - nsfetchedresultscontroller

I have a Core Data financial app that needs to accumulate sales quantities contained in the Trans Entity for each product and then update the totals into the appropriate attribute of the Product Entity.
I am able to achieve this by nesting a for (transArray) inside and tableView (product).
However I need to sort and format the tableView based on the results first.
General Question: Can fetched results be used without tableViews?
- (void)calculateAmounts {
NSIndexPath *indexPath=0;
for (Product *product in self.fetchedResultsController.fetchedObjects){ // All product records
selectedProduct = [self.fetchedResultsController objectAtIndexPath:indexPath];
// >>>>>NSLog shows correct number of object, however selectedProduct # Index Path Are NULL
for (id product1 in transProductArray) { // An array of all of the trans for product
if ((NSNull *)product1 == [NSNull null]) {
}
else if ([product1 isEqualToString:selectedProduct]) {
float qty = [#"1" floatValue];
NSNumber *numQty=[NSNumber numberWithFloat:qty]; // Update quantity sold in product by 1
NSNumber *quantity = [NSNumber numberWithFloat:([selectedProduct.quantitySold floatValue] + [numQty floatValue])];
selectedProduct.quantitySold = quantity;
[self.product.managedObjectContext save:nil];
}
} // Next Trans
} // Next Product
}

This is a good question. Not (clearly) knowing the implementation, I will bet it is possible to use fetched results outside a tableview. However, the Overview of NSFetchedResultsController documentation has this as the first line:
You use a fetched results controller to efficiently manage the results
returned from a Core Data fetch request to provide data for a
UITableView object.
While table views can be used in several ways, fetched results
controllers are primarily intended to assist you with a master list
view.
This infers that the two objects are linked for efficiency and ease of use. Plus, there are monitors under-the-hood to watch for changes, etc.
What you may be looking for is a 'plain-old' NSFetchRequest. A tutorial.

Hi I am not to sure whether you still require an answer to this however I had the same issue myself. I overcame as described in my own question on the subject.
Display multiple core data entities objects in 1 Non-Table View Controller
I inserted a small tableview into my VC and set it to alpha and used the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath to enable displaying details in labels.
This was the only way I could overcome it. Hope this helps.

Thanks for the answer. Like a lot of things the answer was too easy! It's about the fetch, not the tableview. Just do a fetch into an array, then loop though the array to accumulate your values: Also useful for finding a specific object or output to a CSV.
NSError *error;
NSNumber *total=0;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"TransDetail" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (TransDetail *trans in fetchedObjects) {
total = total + trans.amount;
}

Related

Core Data Relationship Designing - Load only desired entity

I need help in designing the database for my application using Core Data.
Namely:
I have 2 Entites called Verse and Translation.
Verse has one-to-many relationship with translation table.
Translation table contains translations in different languages such as EN,DE,AR.
I know if i load the Verse table then corresponding translations will also be lazy loaded.
But i want to load only desired translation of each Verse. E.g only EN.
Because if i load 1000 Verse and with 5 different Translation (DE,EN,TR,AR,FR) then i have a big amount of data which i want to avoid >> 1000x5 = 5000.
I don‘t want load everytime the translations which i dont need. Only the selected translation.
thank you for any advice.
Regards,
Core data does something called faulting, it should only load data you actually ask for. It might load some metadata for all those objects, but it won't actually load the translation until you ask for it. I would suggest looking into faulting more if you want to know more than that because it seems fairly complex. At least the rest of what faulting is seems complex.
i found a solution which is exactly what i wanted. I tested and it returns only the desired Translations.
- (NSArray *) loadAllVersesByLanguage
{
NSManagedObjectContext *_managedObjectContext = [self managedObjectContext];
NSArray *fetchedObjects;
NSString *turkish = #"TR";//should be parameterized
NSString *arabic = #"AR";
NSPredicate *pred = [NSPredicate predicateWithFormat:#"languageCode==%# OR languageCode==%#",turkish,arabic];
NSFetchRequest *fetch = [[NSFetchRequest alloc] initWithEntityName:#"Verse"];
NSError * error = nil;
fetchedObjects = [_managedObjectContext executeFetchRequest:fetch error:&error];
if(fetchedObjects != nil && fetchedObjects.count > 0 ){
for(Verse *verse in fetchedObjects){
NSSet *verseSet = [verse.translations filteredSetUsingPredicate:pred];
[verse.translations setSet:verseSet];
}
return fetchedObjects;
}else{
return nil;
}
return nil;
}

Struggling with CoreData relationships and deleting objects

I'm relatively new to Objective-C and trying to work my way through learning CoreData, albeit with tons of Google searches. I have a good grasp of mySQL and relational tables, but I can't seem to wrap my mind around how to get entities to relate to each other in CoreData.
I like to create little projects for myself when learning something like CoreData, so what I put together was the simple concept of an automotive service tracker (like oil changes, brake work, etc).
I've got 2 entities representing Vehicles and Oil Changes like so ...
and
... as well as their respective relationships. I'm not certain if this would require a one-to-one or one-to-many relationship as a vehicle would have many oil changes, but there would also be many vehicles ... so I'm guessing that's the first thing I need some help understanding.
I'm using SQLLite Manager to watch what's going on with the data model. When I add Vehicles to my Vehicle entity (basically saving UITextFields) I can see that the objects are in the correct entity ...
... and when I add Oil Changes to a particular Vehicle, I can see that those objects are being put in their proper entity as well ...
.. but as you can see from the following, there seems to be a disconnect when trying to associate a particular oil change with a particular vehicle ...
Now, for the important questions ...
1). How do I get individual oil changes to relate only to their respective vehicles? Right now all oil changes are displayed regardless of which vehicle is selected. I'm sure that this would be related to updating my fetched results query, however as you can see from the displayed tables, I'm not getting the respective oil change to the respective vehicle.
2). And this might be solved by understanding question 1 ... but if I delete a particular vehicle, then I obviously need to also delete any oil changes that were related to that vehicle.
My save method is below.
- (IBAction)save:(id)sender {
Oil *oil = [NSEntityDescription insertNewObjectForEntityForName:#"Oil" inManagedObjectContext:self.managedObjectContext];
Vehicle *vehicle = [NSEntityDescription insertNewObjectForEntityForName:#"Vehicle" inManagedObjectContext:self.managedObjectContext];
// Which Vehicle
// newOil.vehicle = whichVehicle.text;
oil.vehicle = vehicle; // not working
// Format: Mileage
NSNumberFormatter *numberFormat = [[NSNumberFormatter alloc] init];
[numberFormat setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *myNumber = [numberFormat numberFromString:oilMileage.text];
oil.oilMileage = myNumber;
// Format: Date
NSDateFormatter *dateFormat = [[NSDateFormatter alloc]init];
[dateFormat setDateFormat:#"MM-dd-yyyy"];
oil.oilDate = [dateFormat dateFromString:oilDate.text];
// Format: Notes
oil.oilNotes = oilNotes.text;
// save context
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate saveContext]; }
->
->
->
->
OK, I'm attempting to integrate what Dan Shelly has suggested (which makes total sense) however I've run into a snag that I need a little more guidance on.
My mainVC isn't using a tableView to display my Vehicles (I've limited the total vehicles to 4 and I'm representing them with icons and tying them back to vehicleIDs in CoreData) ... anyway, since it's not a tableView I can't use the following:
Vehicle *existingVehicle = (Vehicle *)[self.fetchedResultsController objectAtIndexPath:indexPath];
... therefore I'm having trouble creating an instance to the fetched Vehicle object that I can then pass to the OilViewController. I've got a fetchRequest that's returning the correct vehicleID out of CoreData, but I need to know how to actually create the object so that I can pass that over to the OilViewController. My fetchRequest looks like this ...
- (void)fetchObjects {
NSLog(#"DVC-fetching Objects\n\n");
// Create Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Vehicle" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set Search Criteria
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"vehicleID == %#", vehicleID];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *array = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"Object ID: %#", [array valueForKey:#"objectID"]);
NSManagedObjectID *moID = [array valueForKey:#"objectID"];
if (array != nil) {
NSUInteger count = [array count]; // May be 0 if the object has been deleted.
Vehicle *existingVehicle = (Vehicle *)[self.managedObjectContext existingObjectWithID:moID error:&error];
NSLog(#"Count: %lu", (unsigned long)count);
NSLog(#"Fetched Vehicle ID: %#",predicate);
NSLog(#"existingVehicle: %#", existingVehicle);
} else {
// Deal with error.
}
}
but I'm getting the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI isTemporaryID]: unrecognized selector sent to instance 0x11d5b660'
The moID is returning correctly as "0x8bb1980 x-coredata://6681B331-29F0-448B-82F1-4660E033A460/Vehicle/p1"
The array count is returning "1" [correct]
And the FetchVehicle ID is returning "vehicleID == 1" [also correct]
How do I create an instance of Vehicle to this returned object so that I can pass it over??
1) How do I get individual oil changes to relate only to their respective vehicles?
You already do ... only you are currently relating the oil changes to an empty Vehicle object. this is also visible in your vehicle table (Z_PK = [3,4,5]).
When you need to add a new oil change, you probably already have access to the relative Vehicle object this oil change is adde for.
This Vehicle object is probably the one you select in you vehicles view controller.
so instead of inserting a Vehicle object each time you create an oil change, simply set the relationship to an existing vehicle (you are building an object graph, think in terms of existing object and attaching them to each other).
2) Deletion:
from the pictures it seems that you set your deletion rules as they should be in order to achieve your goal. when you delete a Vehicle object the cascade rule will delete the related oil change object along with it. when you delete an oil change object the related Vehicle oil relationship will be nullified.
So to summarise:
Get an existing Vehicle object:
//in your vehicle view controller :
Vehicle* existingVehicle = (Vehicle*)[self.fetchedResultsController objectAtIndexPath:indexPath];
Pass this object along to your "oil change view controller".
When you add a new oil change, set the vehicle to the existing object:
Oil *oil = [NSEntityDescription insertNewObjectForEntityForName:#"Oil" inManagedObjectContext:self.managedObjectContext];
oil.vehicle = existingVehicle; // the one you passed along to this scope
//The rest of the oil content setting
//DON'T FORGET TO SAVE!
You will probably want to change you relationship type to a to-many type of relation ship (a Vehicle may have many oil changes).
This will not change your oil change creation code.
To view oil changes for a specific Vehicle object, you will have to use the existing vehicle object you passed to the oil change view controller and use it in the predicate of the FRC fetch request:
NSPredicate* p = [NSPredicate predicateWithFormat:#"vehicle = %#",existingVehicle];
fetchRequest.predicate = p;

Understanding Core Data Queries(searching data for a unique attribute)

Since I am coming from those programmers who have used sqlite extensively, perhaps I am just having a hard time grasping how Core Data manages to-many relationships.
For my game I have a simple database schema on paper.
Entity: Level - this will be the table that has all information about each game level
Attributes: levelNumber(String), simply the level number
: levelTime(String), the amount of time you have to finish the level(this time will vary with the different levels)
: levelContent(String), a list of items for that level(and that level only) separated by commas
: levelMapping(String), how the content is layed out(specific for a unique level)
So basically in core data i want to set up the database so i can say in my fetchRequest:
Give me the levelTime, levelContent and levelMapping for Level 1.(or
whatever level i want)
How would i set up my relationships so that i can make this type of fetchRequest?
Also I already have all the data ready and know what it is in advance. Is there any way to populate the entity and its attributes within XCode?
As you've described it, it's a single Core Data entity, called Level that has four string attributes. Since there's just the one entity, there are no relationships. You'd create the one entity and add properties so that it looks just like you've described it above:
Getting just one Level is basic Core Data fetching:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Level"];
NSString *levelNumber = #"1";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"levelNumber = %#", levelNumber];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:&error];
NSManagedObject *level = nil;
if ([results count] > 0) {
level = [results objectAtIndex:0];
}
// Use level...
If it was me I'd use one of the numeric types for levelNumber, but maybe you have some reason to use a string there. I'd also probably break levelContent into a separate entity, because (a) comma delimited strings are ugly, no matter how you slice 'em, and (b) you might well want the items to have more attributes, and a separate entity would hold those.

Iterate over NSTableview or NSArrayController to get data

I have an NSTableview which s bound to a NSArrayController. The Table/Arraycontroller contains Core Data "Person" entities. The people are added to the NSTableview by the GUI's user.
Let's say a person entity looks like
NSString* Name;
int Age;
NSString* HairColor;
Now I want to iterate over what is stored in the array controller to perform some operation in it. The actual operation I want to do isn't important I don't really want to get bogged down in what I am trying to do with the information. It's just iterating over everything held in the NSArraycontroller which is confusing me. I come from a C++ and C# background and am new to Cocoa. Let's say I want to build a NSMutableArray that contains each person from nsarraycontroller 1 year in the future.
So I would want to do something like
NSMutableArray* mutArray = [[NSMutableArray alloc] init];
foreach(PersonEntity p in myNsArrayController) // foreach doesn't exist in obj-c
{
Person* new_person = [[Person alloc] init];
[new_person setName:p.name];
[new_person setHairColor:p.HairColor];
[new_person setAge:(p.age + 1)];
[mutArray addObject:new_person];
}
I believe the only thing holding me back from doing something like the code above is that foreach does not exist in Obj-c. I just don't see how to iterate over the nsarraycontroller.
Note: This is for OSX so I have garbage collection turned on
You're looking for fast enumeration.
For your example, something like
for (PersonEntity *p in myNsArrayController.arrangedObjects)
{
// Rest of your code
}
You can also enumerate using blocks. For example:
[myNsArrayController enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop)
{
PersonEntity *p = object;
// Rest of your code
}];
There's pro's and cons to both approaches. These are discussed in depth in the answer to this question:
Objective-C enumerateUsingBlock vs fast enumeration?
You can find a great tutorial on blocks in Apple's WWDC 2010 videos. In that they say that at Apple they use blocks "all the time".

Core Data - get/create NSManagedObject performance

I'm creating an iphone/ipad app that basically reads XML documents and creates tableviews from objects created based on the xml. The xml represents a 'level' in a filesystem. Its basically a browser.
Each time i parse the xml documents i update the filesystem which is mirrored in a core-data sqllite database. For each "File" encountered in the xml i attempt to get the NSManagedObject associated with it.
The problem is this function which i use to get/create either a new blank entity or get the existing one from database.
+(File*)getOrCreateFile:(NSString*)remotePath
context:(NSManagedObjectContext*)context
{
struct timeval start,end,res;
gettimeofday(&start,NULL);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"File" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:1];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"remotePath == %#",remotePath];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items = [context executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
File *f;
if ([items count] == 0)
f = (File*)[NSEntityDescription insertNewObjectForEntityForName:#"File" inManagedObjectContext:context];
else
f = (File*)[items objectAtIndex:0];
gettimeofday(&end, NULL);
[JFS timeval_subtract:&res x:&end y:&start];
count++;
elapsed += res.tv_usec;
return f;
}
For eksample, if i'm parsing a document with 200ish files the total time on a iPhone 3G is about 4 seconds. 3 of those seconds are spent in this function getting the objets from core data.
RemotePath is a unique string of variable length and indexed in the sqllite database.
What am i doing wrong here? or.. what could i do better/different to improve performance.
Executing fetches is somewhat expensive in Core Data, though the Core Data engineers have done some amazing work to keep this hit minimal. Thus, you may be able to improve things slightly by running a fetch to return multiple items at once. For example, batch the remotePaths and fetch with a predicate such as
[NSPredicate predicateWithFormat:#"remotePath IN %#", paths];
where paths is a collection of possible paths.
From the results, you can do the searches in-memory to determine if a particular path is present.
Fundamentally, however, doing fetches against strings (even if indexed) is an expensive operation. There may not be much you can do. Consider fetching against non-string attributes, perhaps by hasing the path and saving the hash in the entity as well. You'll get back a (potentially) larger result set which you could then search in memory for string equality.
Finally, do not make any changes without some performance data. Profile, profile, profile.

Resources