Fast Enumeration of a NSFetchedResult - ios

So I have a NSFetchedResultsController. I have it working fine for normally displaying the data. I have a situation where I need to be able to enumarate through them. So I fetch the results as seen here:
if (![[self fetchedResultsController] performFetch:&error]) {
exit(-1); // Fail
}
I need to do some work with the data before I display it so I am assigning it to an array like this:
arrVacationResults = [fetchedResultsController fetchedObjects];
Works perfectly so far. I now have an array of fetchedObjects. I tried to use fast enumeration but how do I reference whats in each array. I assumed it was a dictionary of sort so I tried to do something like
for (NSDictionary *myVacation in arrVacationResults)
{
}
That fails because in arrVacationResults they are not NSDictionaries, so what are they?

It is an array of NSManagedObjects:
for (NSManagedObject *myVacation in arraVacationResults)
{
//
// if you need to cast it as your entity
//
VacationResultEntity *entity = (VacationResultEntity *) myVacation;
}

Related

How to save data in two NSMutableArray into a single entity using magical record

I'm fetching code & desc from web by calling an API. Then loading it into tableView and based on multiple selection I'm saving the selected values into two arrays i.e. selectedCode and selectedCodeDesc. My Entity is:
So I want to [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error){ but don't know how. I know this much:
- (IBAction)confirmPressed:(id)sender {
NSLog(#"Selected Are: %# - %#",selectedDX,selectedDesc);
for (NSString *code in selectedDX) {
if (!_dxToAddEdit) {
self.dxToAddEdit = [MainCode MR_createEntity];
}
[self.dxToAddEdit setCode:code];
[self.dxToAddEdit setCodeDescription:#""]; //what to give here
[self.dxToAddEdit setSuperBill:_forSuperBill];
}
//after this I'm calling the saveToPersistent
So what to give at setCodeDescription?
If I understood correctly and based on your description and example of code you can do the following:
NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
// Sorry, I renamed selectedCode to selectedCodes and selectedCodeDesc to selectedCodeDescriptions for readability.
// Not sure whether selectedDX is actually selectedCodes.
for (NSInteger i=0; i<selectedCodes.count; ++i) {
NSString *code = selectedCodes[i];
NSString *description = selectedCodeDescriptions[i];
Diagnoses *newDiagnose = [Diagnoses MR_createEntityInContext:defaultContext];
newDiagnose.code = code;
newDiagnose.codeDescription = description;
newDiagnose.superBill = _forSuperBill;
}
[defaultContext MR_saveToPersistentStoreAndWait];
Actually, I would not save the response into two separated arrays. Because of:
Your code becomes difficult to read
Imagine that the model will change and instead of two properties it will contain 4. You will have to create additional arrays.
I would recommend you to parse the response directly into the managed objects. Of course, you may not save them into persistent storage just populate your table view.
I highly recommend you to read these tutorials about Core Data. It will give you insight how to work with Magical Record library. Although, the library simplifies a lot of work it would be better to know what is under the hood ;]

Updating Core Data Properties WITHOUT using Table Views

I have a POS type app that uses Core Data to store daily sales transactions using table views. I am attempting to retrieve and update certain Core Date Properties, like daily sales counts, WITHOUT using table views. Table views use row at index path to point to the correct object (row). I am using the Fetched Results controller with a predicate to retrieve the fetched object (row) Question: How do I obtain the index of the fetched row so that I can retrieve and then update the correct property values? All books and examples use table views to change properties.
Entity Product
Product *product;
______________________________
[self setupFetchedResultsController]; (This returns one object)
product = [NSFetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; (objectAtIndexPath - Errors of course)
I think you shouldn't use NSFetchedResultsController in this case. If you don't want to use it in either a UITableView or a UICollectionView, you're probably better of without it. You're probably better of using a NSFetchRequest instead, it's pretty easy to set up:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"someValue=1"];
fetchRequest.predicate = predicate;
NSError *error = nil;
NSArray *array = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
Now you have a NSArray with all the results, which you could use without having to deal with index paths.
If you're still using a NSFetchedResultController for a table (I'm not sure if you do), those rows will still be updated whenever you make a change.
Update: To update one of the objects returned by the fetch, could be done like this:
Entity *entity = [array firstObject];
[entity setSomeProperty:#"CoreDataIsAwesome"];
NSError *error = nil;
if ([self.managedObjectContext save:&error]) {
NSLog(#"Entity updated!");
} else {
NSLog(#"Something went wrong: %#", error);
}
You can use the method indexPathOfObject: on your fetched results controller to return the index path of the given object to then do your updates.

CoreData updating an NSManagedObject more than once saves several copies?

I have 90 CoreData entities called "ItemModel" with 2 attributes 'uid', 'description', where each of the item is inserted as an NSManagedObject:
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName: #"ItemModel" inManagedObjectContext: AFYDelegate.managedObjectContext];
The first server call assigns the 'uid' to each of the 90 items fetched above for key "uid". The context is not saved here.
On a later second server call, I like to update 'description' for the 90 items, for each of the NSManagedObject using indexPath - by fetching and passing each object to the following method and saving the context:
[self updateItemToDataModel:object withData: description];
....
....
- (void)updateItemToDataModel:(NSManagedObject *) object withData:(NSString *)data
{
[object setValue:data forKey:#"description"];
NSError * error = nil;
if (![self.managedObjectContext save:&error]) {
//Handle any error with the saving of the context
NSLog(#"%#",error.localizedDescription);
}
}
The above works fine in updating CoreData BUT after closing the Simulator and running the code again, there will be two duplicates for each item with the same 'uid' and 'description'. This means I have 180 items now. Repeatedly closing and running the code creates more and more items.
I tried removing updateItemToDataModel method, resetting the Simulator and it works fine with 90 items.
I'm new to CoreData if someone can help. What's wrong with my code if I only wished to update existing items?
You are inserting a new object into the MOC (managed object context) each time--instead of doing a fetch and finding an existing instance of the object you wish to update.
To fetch the existing object you might execute a fetch request like so...
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"uid == %#", uidToMatch];
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"ItemModel" inManagedObjectContext:managedObjectContext]];
NSError * error = nil;
NSArray * results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if ([results count]) {
// you may need to handle more than one match in your code...
// you could also set a fetch limit of 1 and guarantee you only get the first object, eg: [fetchRequest setFetchLimit:1];
}
else {
// no results
}
You might want to wrap that in a helper function so you can re-use it. And read up on NSFetchRequest, NSPredicate and writing predicates in order to do fancier fetch requests.

fetching results twice returns nil

Assume I have 3 files in my project:
data model file, dealing with Core Data and fetching info
viewController 1
viewController 2
In the model file I get results us follows:
- (NSArray *) getColonyData
{
NSManagedObjectContext *cxt = [self managedObjectContext];
NSEntityDescription *colonyDesc = [NSEntityDescription entityForName:#"Colony" inManagedObjectContext:cxt];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:colonyDesc];
NSError *error;
NSArray *colonyResults = [cxt executeFetchRequest:request error:&error];
return colonyResults;
}
I run this part of code in viewDidLoad section of the 1st viewController and I get proper results:
NSArray *colonyResults = [model getColonyData];
if (colonyResults != nil)
{
colonyName.text = [[colonyResults objectAtIndex:0] valueForKey:#"name"];
}
else
{
colonyName.text = #"nothing setup yet";
}
Then I move via a segue to a 2nd viewController, when I execute exactly same code (of course updating different UI elements). But this time the result is nil. What am I doing wrong here? Should I release results manually first? No other errors appear.
Thanks.
As suggested in comments:
experimentModel *model; doesn't assign anything; it just declares that a variable exists. Somewhere, you must be setting model to an actual object for the first controller (and probably not doing that for the second one).
Sending messages to nil objects is a common way to not get the results one was expecting. :)

How to implement re-ordering of CoreData records?

I am using CoreData for my iPhone app, but CoreData doesn't provide an automatic way of allowing you to reorder the records. I thought of using another column to store the order info, but using contiguous numbers for ordering index has a problem. if I am dealing with lots of data, reordering a record potentially involves updating a lot of records on the ordering info (it's sorta like changing the order of an array element)
What's the best way to implement an efficient ordering scheme?
FetchedResultsController and its delegate are not meant to be used for user-driven model changes. See the Apple reference doc.
Look for User-Driven Updates part. So if you look for some magical, one-line way, there's not such, sadly.
What you need to do is make updates in this method:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
userDrivenDataModelChange = YES;
...[UPDATE THE MODEL then SAVE CONTEXT]...
userDrivenDataModelChange = NO;
}
and also prevent the notifications to do anything, as changes are already done by the user:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
if (userDrivenDataModelChange) return;
...
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if (userDrivenDataModelChange) return;
...
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if (userDrivenDataModelChange) return;
...
}
I have just implemented this in my to-do app (Quickie) and it works fine.
Here is a quick example showing a way to dump the fetched results into an NSMutableArray which you use to move the cells around. Then you just update an attribute on the entity called orderInTable and then save the managed object context.
This way, you don't have to worry about manually changing indexes and instead you let the NSMutableArray handle that for you.
Create a BOOL that you can use to temporarily bypass the NSFetchedResultsControllerDelegate
#interface PlaylistViewController ()
{
BOOL changingPlaylistOrder;
}
#end
Table view delegate method:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
// Refer to https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html#//apple_ref/doc/uid/TP40008228-CH1-SW14
// Bypass the delegates temporarily
changingPlaylistOrder = YES;
// Get a handle to the playlist we're moving
NSMutableArray *sortedPlaylists = [NSMutableArray arrayWithArray:[self.fetchedResultsController fetchedObjects]];
// Get a handle to the call we're moving
Playlist *playlistWeAreMoving = [sortedPlaylists objectAtIndex:sourceIndexPath.row];
// Remove the call from it's current position
[sortedPlaylists removeObjectAtIndex:sourceIndexPath.row];
// Insert it at it's new position
[sortedPlaylists insertObject:playlistWeAreMoving atIndex:destinationIndexPath.row];
// Update the order of them all according to their index in the mutable array
[sortedPlaylists enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Playlist *zePlaylist = (Playlist *)obj;
zePlaylist.orderInTable = [NSNumber numberWithInt:idx];
}];
// Save the managed object context
[commonContext save];
// Allow the delegates to work now
changingPlaylistOrder = NO;
}
Your delegates would look something like this now:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
if (changingPlaylistOrder) return;
switch(type)
{
case NSFetchedResultsChangeMove:
[self configureCell:(PlaylistCell *)[self.tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if (changingPlaylistOrder) return;
[self.tableView reloadData];
}
A late reply: perhaps you could store the sort key as a string. Inserting a record between two existing rows can be done trivially by adding an additional character to a string, e.g. inserting "AM" between the rows "A" and "B". No reordering is required. A similar idea could be accomplished by using a floating point number or some simple bit arithmetic on a 4-byte integer: insert a row with a sort key value that is half way between the adjacent rows.
Pathological cases could arise where the string is too long, the float is too small, or there is no more room in the int, but then you could just renumber the entity and make a fresh start. A scan through and update of all your records on a rare occasion is much better than faulting every object every time a user reorders.
For example, consider int32. Using the high 3 bytes as the initial ordering gives you almost 17 million rows with the ability to insert up to 256 rows between any two rows. 2 bytes allows inserting 65000 rows between any two rows before a rescan.
Here's the pseudo-code I have in mind for a 2 byte increment and 2 bytes for inserting:
AppendRow:item
item.sortKey = tail.sortKey + 0x10000
InsertRow:item betweenRow:a andNextRow:b
item.sortKey = a.sortKey + (b.sortKey - a.sortKey) >> 1
Normally you would be calling AppendRow resulting in rows with sortKeys of 0x10000, 0x20000, 0x30000, etc. Sometimes you would have to InsertRow, say between the first and the second, resulting in a sortKey of 0x180000.
I adapted this from method from Matt Gallagher's blog (can't find original link). This may not be the best solution if you have millions of records, but will defer saving until the user has finished reordering the records.
- (void)moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath sortProperty:(NSString*)sortProperty
{
NSMutableArray *allFRCObjects = [[self.frc fetchedObjects] mutableCopy];
// Grab the item we're moving.
NSManagedObject *sourceObject = [self.frc objectAtIndexPath:sourceIndexPath];
// Remove the object we're moving from the array.
[allFRCObjects removeObject:sourceObject];
// Now re-insert it at the destination.
[allFRCObjects insertObject:sourceObject atIndex:[destinationIndexPath row]];
// All of the objects are now in their correct order. Update each
// object's displayOrder field by iterating through the array.
int i = 0;
for (NSManagedObject *mo in allFRCObjects)
{
[mo setValue:[NSNumber numberWithInt:i++] forKey:sortProperty];
}
//DO NOT SAVE THE MANAGED OBJECT CONTEXT YET
}
- (void)setEditing:(BOOL)editing
{
[super setEditing:editing];
if(!editing)
[self.managedObjectContext save:nil];
}
I have implemented the approach of #andrew / #dk with the the double values.
You can find the UIOrderedTableView on github.
feel free to fork it :)
Actually, there's a much simpler way, use a "double" type as an ordering column.
Then whenever you re-order you only EVER need to reset the value of the order attribute for the reordered item:
reorderedItem.orderValue = previousElement.OrderValue + (next.orderValue - previousElement.OrderValue) / 2.0;
I finally gave up on FetchController in edit mode since I need to reorder my table cells as well. I'd like to see an example of it working. Instead I kept with having a mutablearray being the current view of the table, and also keeping the CoreData orderItem atrribute consistent.
NSUInteger fromRow = [fromIndexPath row];
NSUInteger toRow = [toIndexPath row];
if (fromRow != toRow) {
// array up to date
id object = [[eventsArray objectAtIndex:fromRow] retain];
[eventsArray removeObjectAtIndex:fromRow];
[eventsArray insertObject:object atIndex:toRow];
[object release];
NSFetchRequest *fetchRequestFrom = [[NSFetchRequest alloc] init];
NSEntityDescription *entityFrom = [NSEntityDescription entityForName:#"Lister" inManagedObjectContext:managedObjectContext];
[fetchRequestFrom setEntity:entityFrom];
NSPredicate *predicate;
if (fromRow < toRow) predicate = [NSPredicate predicateWithFormat:#"itemOrder >= %d AND itemOrder <= %d", fromRow, toRow];
else predicate = [NSPredicate predicateWithFormat:#"itemOrder <= %d AND itemOrder >= %d", fromRow, toRow];
[fetchRequestFrom setPredicate:predicate];
NSError *error;
NSArray *fetchedObjectsFrom = [managedObjectContext executeFetchRequest:fetchRequestFrom error:&error];
[fetchRequestFrom release];
if (fetchedObjectsFrom != nil) {
for ( Lister* lister in fetchedObjectsFrom ) {
if ([[lister itemOrder] integerValue] == fromRow) { // the item that moved
NSNumber *orderNumber = [[NSNumber alloc] initWithInteger:toRow];
[lister setItemOrder:orderNumber];
[orderNumber release];
} else {
NSInteger orderNewInt;
if (fromRow < toRow) {
orderNewInt = [[lister itemOrder] integerValue] -1;
} else {
orderNewInt = [[lister itemOrder] integerValue] +1;
}
NSNumber *orderNumber = [[NSNumber alloc] initWithInteger:orderNewInt];
[lister setItemOrder:orderNumber];
[orderNumber release];
}
}
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort(); // Fail
}
}
}
If anyone has a solution using fetchController please post it.
So having spent some time on this problem...!
The answers above are great building blocks and without them I would have been lost, but as with other respondents I found that they only partially worked. If you implement them you will find that they work once or twice, then error, or you lose data as you go. The answer below is far from perfect - it's the result of quite a lot of late nights, trial and error.
There are some issues with these approaches:
The NSFetchedResultsController linked to NSMutableArray doesn't guarantee that the context will be updated, so you may see that this works sometimes, but not others.
The copy then delete approach for swapping objects is also difficult behaviour to predict. I found references elsewhere to unpredictable behaviour in referencing an object that had been deleted in the context.
If you use the object index row and have sections, then this won't behave properly. Some of the code above uses just the .row property and unfortunately this could refer to more than one row in a yt
Using NSFetchedResults Delegate = nil, is ok for simple applications, but consider that you want to use the delegate to capture changes that will be replicated to a database then you can see that this won't work properly.
Core Data doesn't really support sorting and ordering in the way that a proper SQL database does. The for loop solution above is good, but there should really be a proper way of ordering data - IOS8? - so you need to go into this expecting that your data will be all over the place.
The issues that people have posted in response to these posts relate to a lot of these issues.
I have got a simple table app with sections to 'partially' work - there are still unexplained UI behaviours that I'm working on, but I believe that I have got to the bottom of it...
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
This is the usual delegate
{
userDrivenDataModelChange = YES;
uses the semaphore mechanism as described above with the if()return structures.
NSInteger sourceRow = sourceIndexPath.row;
NSInteger sourceSection = sourceIndexPath.section;
NSInteger destinationRow = destinationIndexPath.row;
NSInteger destinationSection = destinationIndexPath.section;
Not all of these are used in the code, but it's useful to have them for debugging
NSError *error = nil;
NSIndexPath *destinationDummy;
int i = 0;
Final initialisation of variables
destinationDummy = [NSIndexPath indexPathForRow:0 inSection:destinationSection] ;
// there should always be a row zero in every section - although it's not shown
I use a row 0 in each section that is hidden, this stores the section name. This allows the section to be visible, even when there are no 'live records in it. I use row 0 to get the section name. The code here is a bit untidy, but it does the job.
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSManagedObject *currentObject = [self.fetchedResultsController objectAtIndexPath:sourceIndexPath];
NSManagedObject *targetObject = [self.fetchedResultsController objectAtIndexPath:destinationDummy];
Get the context and source and destination objects
This code then creates a new object which is takes the data from the source, and the section from the destination.
// set up a new object to be a copy of the old one
NSManagedObject *newObject = [NSEntityDescription
insertNewObjectForEntityForName:#"List"
inManagedObjectContext:context];
NSString *destinationSectionText = [[targetObject valueForKey:#"section"] description];
[newObject setValue:destinationSectionText forKeyPath:#"section"];
[newObject setValue: [NSNumber numberWithInt:9999999] forKey:#"rowIndex"];
NSString *currentItem = [[currentObject valueForKey:#"item"] description];
[newObject setValue:currentItem forKeyPath:#"item"];
NSNumber *currentQuantity =[currentObject valueForKey:#"quantity"] ;
[newObject setValue: currentQuantity forKey:#"rowIndex"];
Now create a new object and save the context - this is cheating the move operation - you might not get the new record in exactly the place it was dropped - but at least it will be in the right section.
// create a copy of the object for the new location
[context insertObject:newObject];
[context deleteObject:currentObject];
if (![context save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Now do the for loop update as described above. Note that the context is saved before I do this - no idea why this is needed, but it didn't work properly when it wasn't!
i = 0;
for (NSManagedObject *mo in [self.fetchedResultsController fetchedObjects] )
{
[mo setValue:[NSNumber numberWithInt:i++] forKey:#"rowIndex"];
}
if (![context save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Set the semaphore back and update the table
userDrivenDataModelChange = NO;
[tableView reloadData];
}
Here's what I'm doing that seems to work. For every entity I have a createDate that is used to sort the table by when it was created. It also acts as a unique key. So on the move all I do is swap the the source and destination dates.
I would expect the table to be properly ordered after doing the saveContext, but what happens is the two cells just lay on top of each other. So I reload the data and the order is corrected. Starting the app from scratch shows the records still in the proper order.
Not sure it's a general solution or even correct, but so far it seems to work.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
HomeEntity* source_home = [self getHomeEntityAtIndexPath:sourceIndexPath];
HomeEntity* destination_home = [self getHomeEntityAtIndexPath:destinationIndexPath];
NSTimeInterval temp = destination_home.createDate;
destination_home.createDate = source_home.createDate;
source_home.createDate = temp;
CoreDataStack * stack = [CoreDataStack defaultStack];
[stack saveContext];
[self.tableView reloadData];
}
Try having a look at the Core Data tutorial for iPhone here. One of the sections there talk about sorting (using NSSortDescriptor).
You may also find the Core Data basics page to be useful.

Resources