UITableView Core Data reordering

13,874

I'm not sure which part you are having trouble with (based on the comments)... but here is my suggestion. The displayOrder is just a simple attribute on a NSManagedObject class. If you can save a managed object, then you will be able finish this feature. Lets first take a simple NSManagedObject:

@interface RowObj :  NSManagedObject  
{
}

@property (nonatomic, retain) NSString *rowDescription;
@property (nonatomic, retain) NSNumber *displayOrder;

Next, we need to have local copy of the data being displayed in the tableview. I have read through the comments you have made and I'm not really sure if you are using the FetchedResultsController or not. My suggestion would be to start simple and just use a normal tableviewcontroller where you update the row data whenever a user changes the display order... then save the order when the user is done editing.

The interface for this tableviewcontoller would look like this:

@interface MyTableViewController : UITableViewController {
    NSMutableArray *myTableViewData;
}

@property(nonatomic,retain) NSMutableArray *myTableViewData;

@end 

Next, we need to load the the table view data in the viewWillAppear method:

- (void)viewWillAppear:(BOOL)animated {
    myTableViewData = [helper getRowObjects]; // insert method call here to get the data
    self.navigationItem.leftBarButtonItem = [self editButtonItem];
}

There are 2 things going on here... (I'll explain the editButtonItem later) the first is that we need to get our data from CoreData. When I have to do this I have some sort of helper(call it what you want) object do the work. A typical find method would look like this:

- (NSMutableArray*) getRowObjects{
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"RowObj" inManagedObjectContext:[self managedObjectContext]];
    [request setEntity:entity];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"displayOrder" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [request setSortDescriptors:sortDescriptors];
    [sortDescriptors release];
    [sortDescriptor release];

    NSError *error;
    NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
    if (mutableFetchResults == nil) {
        // Handle the error.
    }
    [request release];
    return mutableFetchResults;
}

Now that you have your data, you can now wait for the user to edit the table. That is where the [self editButtonItem] comes into play. This is a built in feature that returns a bar button item that toggles its title and associated state between Edit and Done. When the user hits that button, it will invoke the setEditing:animated: method:

To update the display order you need to override the setEditing method on the UITableViewController class. It should look something like this:

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
    [super setEditing:editing animated:animated];
    [myTableView setEditing:editing animated:animated];
    if(!editing) {
        int i = 0;
        for(RowObj *row in myTableViewData) {
            row.displayOrder = [NSNumber numberWithInt:i++];
        }
        [helper saveManagedObjectContext]; // basically calls [managedObjectContext save:&error];
    }
}

We don't have to do anything when the user is in edit mode... we only want to save once they have pressed the "Done" button. When a user drags a row in your table you can update your display order by overriding the canMoveRowAtIndexPath and moveRowAtIndexPath methods:

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
    return true;
}

(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {

    RowObj *row = [myTableViewData objectAtIndex:sourceIndexPath.row];
    [myTableViewData removeObjectAtIndex:sourceIndexPath.row];
    [myTableViewData insertObject:row atIndex:destinationIndexPath.row];
}

Again, the reason I don't update the displayOrder value here is because the user is still in edit mode... we don't know if the user is done editing AND they could even cancel what they've done by not hitting the "Done" button.

EDIT

If you want to delete a row you need to override tableView:commitEditingStyle:forRowAtIndexPath and do something like this:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the managed object at the given index path.
        RowObj *row = [myTableViewData objectAtIndex:indexPath.row];
        [helper deleteRow:row];

        // Update the array and table view.
        [myTableViewData removeObjectAtIndex:indexPath.row];
        [myTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
    }   
}
Share:
13,874

Related videos on Youtube

indragie
Author by

indragie

iOS and Mac Developer. I'm working on Flamingo for Mac and previously built Sonora.

Updated on July 03, 2020

Comments

  • indragie
    indragie almost 4 years

    I know this question has been asked before, and I took a look at the answer to this question. However, I'm still confused as to how to implement reordering with a UITableView in a Core Data project. What I know is that I need to have a "displayOrder" attribute in my Entity to track the order of items, and I need to enumerate through all the objects in the fetched results and set their displayOrder attribute.

    In the given code in the question I linked to, the table view delegate method calls a method like this [self FF_fetchResults];, and the code for that method is not given so its hard to tell what exactly it is.

    Is there any sample code that demonstrates this? That would be simpler to look at than sharing large chunks of code.

    Thanks

    • gerry3
      gerry3 about 14 years
      Have you tried the linked code? If you are using a fetched results controller, you may not need to do anything. That method is just fetching the objects again (now that the sort has changed).
    • indragie
      indragie about 14 years
      Thanks, I'll try it. Is it necessary to fetch the objects again after the order has changed?
    • gerry3
      gerry3 about 14 years
      You should not need to if you are using a fetched results controller (and have boiler plate code to update the table view when the fetched results controller changes).
    • indragie
      indragie about 14 years
      I just realized that the linked code does not seem to use NSManagedObjectContext and NSFetchedResultsController (as far as I can see). I tried adapting it to my needs by using NSManagedObject instead of FFObject and saving the context at the end. But this doesn't work at all, the UI completely messes up when I move a row (rows disappear, others get their positions switched, just overall chaos). Are there any sample projects/open source projects that implement this?
  • indragie
    indragie about 14 years
    Thanks for the detailed answer, I'll try this out.
  • Ryan Ferretti
    Ryan Ferretti about 14 years
    Sure, let me know if you have any problems... this is basically word-4-word from a working implementation from one of my apps so it should help you get it done.
  • indragie
    indragie about 14 years
    What about deleting rows? Is there anything special that needs to be done there to work with this?
  • Ryan Ferretti
    Ryan Ferretti about 14 years
    added deleting to the answer... glad it worked for you! Good Luck
  • chrish
    chrish almost 14 years
    When you display rows, are you referencing the managed object in NSMutableArray or NSFetchedResultsController? A complete example of the whole controller would be very useful! Thanks so much for the help so far.
  • Yang Meyer
    Yang Meyer over 11 years
    Ryan, this is a nice write-up and indeed does what it says. Be aware that using this approach everything is loaded up front, vs on-demand with NSFetchedResultsController. And 2 pedantic corrections: the last parameter for -deleteRows…:withRowAnimation: is not a BOOL, and -tableView:canMoveRowAtIndexPath: should return YES, not true.
  • Ranjit
    Ranjit about 11 years
    Hi Ryan, if I want to show the entities in descending Order then. How should I proceed