views:

1542

answers:

2

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

+9  A: 

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.

Hope this helps... if you a having trouble getting CoreData to save... then I would change the title of the question and check out the simple tutorial.

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];
    }   
}
Ryan Ferretti
Thanks for the detailed answer, I'll try this out.
macatomy
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.
Ryan Ferretti
What about deleting rows? Is there anything special that needs to be done there to work with this?
macatomy
added deleting to the answer... glad it worked for you! Good Luck
Ryan Ferretti
Thanks! Really appreciate it.
macatomy
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.
chrish
A: 

Hi May be you can help me. I did all as described, everything works except the part when i call save. It crashes with message "Serious application error. Exception was caught during Core Data change processing: * -[NSCFArray removeObjectAtIndex:]: index (0) beyond bounds (0) with userInfo (null)". Any idea what im doing wrong?

EDIT:My bad. Crash was caused by wrong processing of didChangeObject event. I forgot that when you call save for managed object it will cause didChangeObject call.

AlexS