Reputation: 16725
I have an NSOrderedSet
attached to a Core Data object that's passed into a UITableViewController
. The managedObjectContext
is alive and well in the UITableViewController
and myNSOrderedSet
displays properly my tableView
. When I go to reorder myNSOrderedSet
, the app crashes. I put a breakpoint in moveRowAtIndexPath
to see what's going on and everything's fine up until I try to save it.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
// BREAKPOINT: "po self.routineToEdit.exercises" shows order is 0, 1, 2
NSMutableOrderedSet *orderedSet = [self.routineToEdit.exercises mutableCopy];
[orderedSet exchangeObjectAtIndex:fromIndexPath.row withObjectAtIndex:toIndexPath.row];
// BREAKPOINT: "po self.routineToEdit.exercises" shows order is 2, 1, 0
self.routineToEdit.exercises = orderedSet;
[self saveContext];
}
- (void)saveContext {
if (self.managedObjectContext != nil) {
NSError *error = nil;
if ([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}
Rather than passing my object into MyUITableViewController
, I'm tempted to use a fetchedResultsController
to grab my object and display it on MyUITableViewController
. Before I go that route, I'd like to see I can re-order an NSOrderedSet
with the object that's passed in from AnotherUITableViewController
.
Here's the console output from my crash:
2015-07-12 06:39:53.176 MyApp[26505:1105589] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString appendString:]: nil argument'
*** First throw call stack:
(
0 CoreFoundation 0x00000001060dec65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x0000000105663bb7 objc_exception_throw + 45
2 CoreFoundation 0x00000001060deb9d +[NSException raise:format:] + 205
3 CoreFoundation 0x00000001060afabf mutateError + 159
4 CoreData 0x000000010591ec26 -[_NSSQLGenerator prepareMasterReorderStatementPart2ForRelationship:] + 118
5 CoreData 0x000000010598cfb8 -[NSSQLAdapter newCorrelationMasterReorderStatementPart2ForRelationship:] + 72
6 CoreData 0x00000001059a5671 -[NSSQLiteConnection writeCorrelationMasterReordersFromTracker:] + 817
7 CoreData 0x00000001059a5f81 -[NSSQLiteConnection writeCorrelationChangesFromTracker:] + 65
8 CoreData 0x0000000105997627 -[NSSQLCore writeChanges] + 1351
9 CoreData 0x00000001058d32c7 -[NSSQLCore saveChanges:] + 423
10 CoreData 0x00000001058a3e74 -[NSSQLCore executeRequest:withContext:error:] + 484
11 CoreData 0x000000010597ee3f __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke + 4335
12 CoreData 0x0000000105987c30 gutsOfBlockToNSPersistentStoreCoordinatorPerform + 192
13 libdispatch.dylib 0x0000000108890614 _dispatch_client_callout + 8
14 libdispatch.dylib 0x0000000108876002 _dispatch_barrier_sync_f_invoke + 365
15 CoreData 0x0000000105979245 _perform + 197
16 CoreData 0x00000001058a3a58 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 504
17 CoreData 0x00000001058cd52d -[NSManagedObjectContext save:] + 1213
18 MyApp 0x000000010511083e -[EditRoutineTableViewController saveContext] + 222
19 MyApp 0x0000000105110673 -[EditRoutineTableViewController tableView:moveRowAtIndexPath:toIndexPath:] + 371
20 UIKit 0x0000000106817822 -[UITableView _endReorderingForCell:wasCancelled:animated:] + 565
21 UIKit 0x000000010682b89d -[UIControl touchesEnded:withEvent:] + 462
22 UIKit 0x0000000106767958 -[UIWindow _sendTouchesForEvent:] + 735
23 UIKit 0x0000000106768282 -[UIWindow sendEvent:] + 682
24 UIKit 0x000000010672e541 -[UIApplication sendEvent:] + 246
25 UIKit 0x000000010673bcdc _UIApplicationHandleEventFromQueueEvent + 18265
26 UIKit 0x000000010671659c _UIApplicationHandleEventQueue + 2066
27 CoreFoundation 0x0000000106012431 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
28 CoreFoundation 0x00000001060082fd __CFRunLoopDoSources0 + 269
29 CoreFoundation 0x0000000106007934 __CFRunLoopRun + 868
30 CoreFoundation 0x0000000106007366 CFRunLoopRunSpecific + 470
31 GraphicsServices 0x000000010a12ba3e GSEventRunModal + 161
32 UIKit 0x00000001067198c0 UIApplicationMain + 1282
33 MyApp 0x000000010510a05f main + 111
34 libdyld.dylib 0x00000001088c4145 start + 1
Thank you for reading. I welcome your input.
I found this issue that's in the "same neighborhood" of the problem I'm encountering, however the order of my set is not static like the example given in the example.
Core Data ordering with a UITableView and NSOrderedSet
Upvotes: 1
Views: 635
Reputation: 16725
The StackOverflow consensus seems to be there's a bug with how NSOrderedSet
relationships are handled by Core Data. I was able to manipulate the order of my NSOrderedSet
using the information contained in Dmitry Makarenko's post on the accessors generated by Xcode:
Exception thrown in NSOrderedSet generated accessors
I also cleaned up (at least I think so) the code I used for moving rows in the UITableView
:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
NSMutableOrderedSet *orderedSet = [self.routineToEdit mutableOrderedSetValueForKey:@"stretches"].mutableCopy;
// This is necessary, as exchangeObjectAtIndex:withObjectAtIndex doesn't work passing in indexPath.row
NSInteger fromIndex = fromIndexPath.row;
NSInteger toIndex = toIndexPath.row;
// Swap objects at indexes
[orderedSet exchangeObjectAtIndex:fromIndex withObjectAtIndex:toIndex];
NSLog(@"ending orderedSet = /n%@", orderedSet);
// Assign NSMutableOrderdSet to the object's NSOrderedSet
self.routineToEdit.stretches = orderedSet;
// Added performBlockAndWait so the save is complete before doing anything else
[self.persistentStoreCoordinator performBlockAndWait:^{
[self saveContext];
}];
}
Saving the object to the managedObjectContext
right after the UITableView rows/NSObjects are swapped is critical--I discovered this when I saw not all my changes were being saved when I clicked a Done IBAction
w/ the save method invoked there:
[self.persistentStoreCoordinator performBlockAndWait:^{
[self saveContext];
}];
...but the app still crashed.
Finally, I changed the relationship between BOTH entities to ordered and that did the trick:
For my purposes, this is sufficient. I tried each of these fixes independently and none of them worked on a stand-alone basis. At this point, my app works the way I want it to, so I'm going to leave it alone for now.
Upvotes: 1
Reputation: 17500
You are already halfway there by calling mutableOrderedSetValueForKey:
, don't call mutableCopy
on it! Also, do not reassign this mutable ordered set back to exercises
. just save your context, call refreshObject:mergeChanges:
on the context, and exercises
should reflect your changes.
Upvotes: 0
Reputation: 80273
I think the fetch results controller does not give you any advantage here (mainly due to the sorting requirement which does not make sense here).
I would just use the ordered set (the to-many relationship of the object being edited) directly to inform your table view data source. And I would check that assigning the newly ordered ordered set back to the object is working as expected.
Upvotes: 1