Reputation: 21
I have a bug in here somewhere and I can not find it so I am hoping your keen eyes will!
I am using a FRC with a tableView. the FRC is section sorted by keyPath and then sorted by "displayOrder" - the usual.
The Details "displayOrder" in each section start at 1 so when I insert an item, in another method, it goes to index 0 of the section.
I want to loop through the affected section(s) and re-assign the "displayOrder" starting at 1.
During re-order, the code works for: Re-ordering within the any section AS LONG AS the re-ordered cell moves up and not down.
Code does not work for... clicking on a cell but not moving it.. the code changes the order for some reason thus changing the order of the cells. - when I click a cell, it along with the other cells above it in the same section re-order.
I used to have this working and I don't know what happened.
Thanks for any help.
-Edited-
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
NSError *error = nil;
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
TheDetail *fromThing = [self.fetchedResultsController objectAtIndexPath:fromIndexPath];
TheDetail *toThing = [self.fetchedResultsController objectAtIndexPath:toIndexPath];
NSPredicate *catetgoryPredicate = [NSPredicate predicateWithFormat:@"relationshipToTheCategory.name == %@", fromThing.relationshipToTheCategory.name];
NSMutableArray *allThings = [[[self.fetchedResultsController fetchedObjects] filteredArrayUsingPredicate:catetgoryPredicate] mutableCopy];
NSPredicate *fromPredicate = [NSPredicate predicateWithFormat:@"relationshipToTheSection.name == %@", fromThing.relationshipToTheSection.name];
NSPredicate *toPredicate = [NSPredicate predicateWithFormat:@"relationshipToTheSection.name == %@", toThing.relationshipToTheSection.name];
[allThings removeObject:fromThing];
[allThings insertObject:fromThing atIndex:toIndexPath.row];
//if the sections are NOT the same, reorder by section otherwise reorder the one section
if (![fromThing.relationshipToTheSection.name isEqual:toThing.relationshipToTheSection.name]) {
//Change the from index section's relationship and save, then grab all objects in sections and re-order
[fromThing setRelationshipToTheSection:toThing.relationshipToTheSection];
if ([context save:&error]) {
NSLog(@"The setting section save was successful!");
} else {
NSLog(@"The setting section save was not successful: %@", [error localizedDescription]);
}
NSMutableArray *fromThings = [[allThings filteredArrayUsingPredicate:fromPredicate]mutableCopy];
NSInteger i = 1;
for (TheDetail *fromD in fromThings) {
[fromD setValue:[NSNumber numberWithInteger:i] forKey:@"displayOrder"];
i++;
}
//reset displayOrder Count, the re-order the other section
i = 1;
NSMutableArray *toThings = [[allThings filteredArrayUsingPredicate:toPredicate]mutableCopy];
for (TheDetail *toD in toThings) {
[toD setValue:[NSNumber numberWithInteger:i] forKey:@"displayOrder"];
i++;
}
} else {
NSMutableArray *fromThings = [[allThings filteredArrayUsingPredicate:fromPredicate]mutableCopy];
NSInteger i = 1;
for (TheDetail *fromD in fromThings) {
[fromD setValue:[NSNumber numberWithInteger:i] forKey:@"displayOrder"];
i++;
}
}
if ([context save:&error]) {
NSLog(@"The save was successful!");
} else {
NSLog(@"The save was not successful: %@", [error localizedDescription]);
}
FRC
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSManagedObjectContext *context = [[self appDelegate]managedObjectContext];
//Construct the fetchResquest
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *detail = [NSEntityDescription entityForName:@"TheDetail" inManagedObjectContext:context];
[fetchRequest setEntity:detail];
//Add predicate
NSString *category = @"1";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"relationshipToTheCategory == %@", category];
[fetchRequest setPredicate:predicate];
//Add sort descriptor
NSSortDescriptor *sortDescriptor2 = [NSSortDescriptor sortDescriptorWithKey:@"relationshipToTheSection.displayOrder" ascending:YES];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"displayOrder" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor2, sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//Set fetchedResultsController
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:@"relationshipToTheSection.name" cacheName:@"Root"];
NSError *error = nil;
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
[self.fetchedResultsController performFetch:&error];
return _fetchedResultsController;
New Error
Section *toSection = [[self fetchedResultsController] sections][[toIndexPath section]];
NSString *toSectionName = [[[toSection objects] lastObject] name];
Here I get the error in the IB "No visible @interface for "DSection" declares the selector 'objects'.
Upvotes: 1
Views: 1291
Reputation: 46718
Don't remove yourself as the delegate for the NSFetchedResultsController
. That is against the intended design of that class. If that is "helping" then it is masking a real problem.
Don't call -performFetch;
from this method. The NSFetchedResultsController
will detect the changes and tell your delegate about them.
Don't call -reloadData
from this method. Let the delegate methods of NSFetchedResultsController
do the reordering.
Always, always, always capture the error on a core data save. Even though you really don't need to save here (this is a bad time to block the UI with a save), you should ALWAYS capture the error and then watch for the result otherwise errors are hidden.
It is not clear what the -save:
is doing. You haven't changed anything by the point of that save.
So that is a lot of work you are doing that you don't need to do. You are fighting the framework and making things harder.
Your reordering logic is more complicated than it needs to be, I think. It would help to see the NSFetchedResultsController
initialization as well. But I am guessing you have sections based on name
and then order by displayOrder
. If that is the case this code can be a lot cleaner which would then make the issue more apparent.
My question to you is, are you checking this with breakpoints? Is this code firing when a row doesn't get actually moved? Should you check to see if your toIndexPath
and fromIndexPath
are equal?
You do not need to save your context here. This is a UI method, saving causes delays which will make the UI slow to respond. Save later.
You do not need to run a NSFetchRequest here. That also hits disk and causes delays in the UI. Every piece of information that you need is already in memory inside of your NSFetchedResultsController
. Use the existing object relationships to retrieve the data you are needing to make your decisions.
Calling entities The*
is against Objective-C naming conventions. Words like "the", "is", "are" do not belong in entity or class names.
Consider this version of your code:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSManagedObjectContext *context = [[self fetchedResultsController] managedObjectContext];
TheDetail *fromThing = [[self fetchedResultsController] objectAtIndexPath:fromIndexPath];
Section *toSection = [[self fetchedResultsController] sections][[toIndexPath section]];
NSString *toSectionName = [[[toSection objects] lastObject] name];
NSString *fromSectionName = [[fromThing relationshipToTheSection] name];
if ([toSectionName isEqualToString:fromSectionName]) {
//Same section, easy reorder
//Move the object
NSMutableArray *sectionObjects = [[[[self fetchedResultsController] sections][[fromIndexPath section]] objects] mutableCopy];
[sectionObjects removeObject:fromThing];
[sectionObjects insertObject:fromThing atIndex:[toIndexPath row]];
//Reorder
NSInteger index = 1;
for (TheDetail *thing in sectionObjects) {
[thing setValue:@(index) forKey:@"displayOrder"];
}
return; //Early return to keep code on the left margin
}
NSMutableArray *sectionObjects = [[[[self fetchedResultsController] sections][[fromIndexPath section]] objects] mutableCopy];
[sectionObjects removeObject:fromThing];
//Reorder
NSInteger index = 1;
for (TheDetail *thing in sectionObjects) {
[thing setValue:@(index) forKey:@"displayOrder"];
}
if ([[toSection numberOfObjects] count] == 0) {
[fromThing setValue:@(0) forKey:@"displayOrder"];
//How do you determine the name?
return;
}
sectionObjects = [[toSection objects] mutableCopy];
[sectionObjects insertObject:fromThing atIndex:[toIndexPath row]];
//Reorder
NSInteger index = 1;
for (TheDetail *thing in sectionObjects) {
[thing setValue:@(index) forKey:@"displayOrder"];
}
}
There is no fetching and no saving. We are working with only what is in memory already so it is VERY fast. This should be C&P-able except for one of the comments I left in.
Upvotes: 12