I am displaying an entity called Skills in a UITableViewController.
I fetch the results like this in the viewDidLoad:
-(void)fetchTableData {
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Skills" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
self.skillsArray = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
}
Also my cell for index path is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
// Configure the cell...
Skills *skill = self.skillsArray[indexPath.row];
// Skills is a NSManagedObject, I added the Skills.h file.
[cell.textLabel setText:skill.nameOfSkill];
return cell;
}
And I am adding new NSManagedObject *newSkill to Core Data by using UIAlertView with a text field in the delegate method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
NSLog(#"Add button clicked");
NSString *newSkillText = [alertView textFieldAtIndex:0].text;
NSManagedObjectContext *context = [self managedObjectContext];
Skills *newSkill = [NSEntityDescription insertNewObjectForEntityForName:#"Skills" inManagedObjectContext:context];
newSkill.nameOfSkill = newSkillText;
[self.skillsArray addObject:newSkill];
} else {
// Do something else
}
[self.tableView reloadData];
}
Every time I reload the data the cells are displaying the data in the order the data was added but if dismiss the view controller and return the cells display the data in a different order than added? The weird part is that I am using this same exact code to add core data and retrieve it in another UITableViewController and it never displays out of order. The data added in this UITableViewController is as follows: I am pushing to another UIViewController and add the information there and then dismiss back to the tableview. In this code I am adding the information while in the view controller is being presented, maybe that could have something to do with it?
Also I know I could add an NSSortDiscriptor such as:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"nameOfSkill" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
but it does it by the name and I want by the way it was added without having to add another attribute for index cause I never did that to my other data and it always displays in the order it was added.
You'll have to add an Attribute to sort on...either an updating, incrementing counter, or the timestamp of the insertion. If you subclass NSManagedObject, ou can write this value in -awakeFromInsert. Then your tableview's fetch request will sort on that attribute.
You won't get the data as it is. You will have to insert a field of "Time" and you can sort according to it.
OR
Add a unique field of 'data_id' . Always check the count before inserting the data. And give the data_id accordingly adding 1 to the count. Then after fetching the data from core data sort it as per data_id.
You can do as per you like.
For the desired result you need to sort it according to timestamp or primary key which is auto-generated by core data database.
Core Data makes its own primary key - you don't have to add one. You can retrieve it with
NSManagedObjectID *moID = [managedObject objectID];
I have a working app so far which basically consists of the following:
Using Core Data, I have 1 Table View Controller with an Add Button which modally calls up a new View Controller prompting the user to add text into three fields. There is also a selection field where the user has to choose between "bought" and "sold". When the user clicks save, the entry is added to the table view controller as a subtitle cell with the information filled in. It works well right now without the bought and sold aspect.
What I would like to do is simply change the color of the table view cell to be green for sold and red for bought. So when a user goes to add the information, they fill in the required fields and also choose bought or sold and then when clicking save, the table view cell displays either the green or red for each entry.
I am adding the tableView datasource and delegate methods here in the current TableViewController. With this, I am basically looking into a "Transaction" entity and fetching relationships to other Entities. The "Status" (Bought/Sold) is also in a related Entity to the Transaction Entity, in it's own entity called Purchase. So Transaction has a relationship called status.action (action being the inverse attribute to the Transaction).
Here's the code so far from the TableView:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.transactions.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Persons";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSManagedObject *transaction = [self.transactions objectAtIndex:indexPath.row];
[cell.textLabel setText:[NSString stringWithFormat:#"%# %#", [transaction valueForKeyPath:#"whoBy.name"], [transaction valueForKeyPath:#"gifting.amount"]]];
[cell.detailTextLabel setText:[NSString stringWithFormat:#"%#", [transaction valueForKeyPath:#"occasion.title"]]];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[self.managedObjectContext deleteObject:[self.transactions objectAtIndex:indexPath.row]];
[self.transactions removeObjectAtIndex:indexPath.row];
[self.tableView reloadData];
NSError *error = nil;
if (![self.managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
[self.tableView reloadData];
}
}
}
The code from the Modal that actually goes ahead and allows the user to add the entries text (as well as select the Bought/Sold which is not implemented yet) looks like this:
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *transaction = [NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:context];
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
NSManagedObject *occasionEvent = [NSEntityDescription insertNewObjectForEntityForName:#"Occasion" inManagedObjectContext:context];
NSManagedObject *amountType = [NSEntityDescription insertNewObjectForEntityForName:#"Gift" inManagedObjectContext:context];
[person setValue:self.nameTextField.text forKey:#"name"];
[occasionEvent setValue:self.occasionTextField.text forKey:#"title"];
[amountType setValue:self.amountTextField.text forKey:#"amount"];
[transaction setValue:person forKey:#"whoBy"];
[transaction setValue:occasionEvent forKey:#"occasion"];
[transaction setValue:amountType forKey:#"gifting"];
Any help would be appreciated.
Thanks
Rather than trying to do this manually, all you need to do is read the state of the cell's object from the core data store and set the background colour appropriately.
I would also recommend using an NSFetchedResultsController as you datasource. It's designed to work well with Core Data and tableviews, and if you set up the delegates properly, it will even respond to changes in the model without any intervention from you.
edit
Following on from the comments you've added, I can see what the problem is.
Core Data stores objects not values. So when you try and put values into a core data store you turn them into objects first. And when you get the object out of core data you need to translate it into the value you want.
You can see this in what you are doing yourself when you add objects to your managed object:
[transaction setValue:#(self.wasOptions.selectedSegmentIndex == 0) forKey:#"wasReceived"];
This expression returns a BOOL
self.wasOptions.selectedSegmentIndex == 0
A BOOl is a value and can't be stored into Core Data, so you turn it into an NSNumber representation with:
#(self.wasOptions.selectedSegmentIndex == 0)
So you now have an NSNumber object representing the BOOL value of YES or NO stored in core data.
When you try and get the value for your key:
[transaction valueForKey:#"wasReceived"]
This is returning the NSNumber representation not the BOOL value it represents
In order to get the actual BOOL, use a convenient method from NSNumber boolValue. So to fix your problem replace this expression:
[transaction valueForKey:#"wasReceived"]
with this expression:
[[transaction valueForKey:#"wasReceived"] boolValue]
which returns a proper BOOL value that your conditional can operate on.
I have a UITableView With a custom Cell that contains another UItableView. I need to pass an object to it, and then execute a fetchrequest based on this object.
From my cellForRowAtIndexPath method, I am passing a Managed Object to the custom Cell, like this:
TWHistoryViewStandardExpendedCell *cell = (TWHistoryViewStandardExpendedCell *)[tableView dequeueReusableCellWithIdentifier:ExpandedCell];
if (cell == nil) {
cell = (TWHistoryViewStandardExpendedCell *)[[[NSBundle mainBundle] loadNibNamed:#"HistoryViewStandardCellExpanded" owner:self options:nil] objectAtIndex:0];
}
Day *aDay = (Day *)[_fetchedResultsController objectAtIndexPath:indexPath];
[cell setViewingDay: aDay]; // NSLog here returns the expected object! :)
return cell;
This way, I should be able to execute a fetchRequest based on this object, from my custom Cell.
On my custom UItableViewCell, I do this:
- (void) awakeFromNib
{
[self fetchRequest];
}
- (void)fetchRequest {
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
Some details about my fetchedResultsController: see in comments that my managed object returns null here!
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController) {
return _fetchedResultsController;
}
NSManagedObjectContext * managedObjectContext =
[myAppDelegate managedObjectContext];
NSEntityDescription *entity =
[NSEntityDescription entityForName:#"Clock"
inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat:#"day == %#", _viewingDay]];
NSLog(#"_viewingDay: %#", _viewingDay); // returns null! :(
NSLog(#"_viewingDay.clocks: %#", _viewingDay.clocks); // also returns null!
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"clockIn" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
//
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
_fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Based on this, I have overriden my setViewingDay method:
- (void)setViewingDay:(Day *)viewingDay
{
if (_viewingDay != viewingDay) {
_viewingDay = viewingDay;
NSLog(#"setViewingDay: %#", _viewingDay); // returns expected object! =)
[self fetchRequest];
}
}
Still, after this, my UITableView remains empty. My numberOfRowsInSection method keeps returning 0!
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
NSLog(#"numberOfRowsInSection %d", [sectionInfo numberOfObjects]);
return [sectionInfo numberOfObjects];
}
I also added a UIButton that performs the fetch request. Hoping to find a situation where the fetchRequest was being performed before the actual setting of my reference to the managed object. So I click on it, do perform a fetch. And nothing! No rows whatsoever.
EDIT:
However, the NSLogs bellow, from inside my fetchResultsController do return an object then, by using a supporting UIBUtton and IBAction. But still no rows!
NSLog(#"_viewingDay: %#", _viewingDay);
NSLog(#"_viewingDay.clocks: %#", _viewingDay.clocks);
Supporting UIButton to perform a fetch:
- (IBAction)fetchem:(UIButton *)sender {
[_clocksTableView reloadData];
_fetchedResultsController = nil;
[NSFetchedResultsController deleteCacheWithName:nil];
[self fetchRequest];
NSLog(#"\n\nfetchem: %# \n\nvd: %# \n\nclocks: %# ", _fetchedResultsController.description, _viewingDay.description, _viewingDay.clocks.description );
}
Anything I might be missing here?
Thank you!
EDIT 2:
I just realized that setting my predicate to (1 == 1), returns all clocks. Confirming that my resultsController is correctly set up. There may be something wrong with my predicate... I don't see what. I have a similar predicate in a previous controller and works great.
Predicate is very simple, nothing fancy:
[fetchRequest setPredicate: [NSPredicate predicateWithFormat:#"day == %#", _viewingDay]];
I have a Clock and Day entities.
A Day has many clocks. 1 to many relationship.
A clock has one day, and one day only.
The fetchRequest above should have been returning all clocks from that day. But it's not?
1) awakeFromNib gets execute the moment you instantiate the cell and therefore before you set the viewing day. It also doesn't get execute if the cell is reused, so you should trigger the fetch request separately.
2) How does the fetchedResultsController property inside the cell get set?
EDIT 1:
From the datamodel and the predicate, it would seem the relationship between clock and day doesn't get set correctly.
EDIT 2:
Quoting the asker's comment from below: "The problem was fixed after inserting: [self fetchRequest] inside my overriden setter."
I use core data with magical record and i'm try to filter data with a search bar in a table view.
I write two methods to get the number of rows and the name of the cells:
-(int) dammiNumeroCercati:(NSString *)searchBar
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"nome CONTAINS [cd] %#", searchBar];
NSArray*arra = [Ricetta MR_findAllSortedBy:#"nome" ascending:YES withPredicate:predicate];
return arra.count;
}
-(NSString*) dammiNomeRicettaCercata:(NSString *)searchBar mostrataNellaCella: (int) cella
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"nome CONTAINS [cd] %#", searchBar];
NSArray *arra = [Ricetta MR_findAllSortedBy:#"nome" ascending:YES withPredicate:predicate];
Ricetta*ctn = arra[cella];
return [NSString stringWithFormat:#"%#", ctn.nome];
}
then i call this method inside the numberOfRowsInSection: and cellForRowAtIndexPath: inside an if cycle:
if (self.mySearchBar.isFirstResponder){
// the above methods
} else {
// the normals methods to have all the data
}
somebody know where I'm wrong or if I miss somethings?
searchBar is usually a UISearchBar, not a string.
You should use searchBar.text and process that in your methods.
Also, in your table view's datasource methods you have to make sure which table view is causing the callback, and then return the correct count/string. Usually this is checked by comparing pointers to the two tables (original table view and search results table view).
-(NSUInteger)tableView:(UITableView*)tableView
numberOfRowsInSection:(NSUInteger)section {
if (tableView == _tableView) {
// return the usual row count
}
return [self dammiNumeroCercati:_searchBar.text];
}
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.