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?
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
Table view delegate method:
Your delegates would look something like this now:
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.
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 :)
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...
This is the usual delegate
uses the semaphore mechanism as described above with the if()return structures.
Not all of these are used in the code, but it's useful to have them for debugging
Final initialisation of variables
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.
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.
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.
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!
Set the semaphore back and update the table
}
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:
and also prevent the notifications to do anything, as changes are already done by the user:
I have just implemented this in my to-do app (Quickie) and it works fine.
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: