I have a table view and I just implemented a class that helps me reorder the cells, like the regular moving cells method that comes with the table view delegate.
Now after I reorder the cells, I need to change the array that holds the cell objects to the new order... How do I do that?
This is my method for reordering the cells:
- (void)moveTableView:(FMMoveTableView *)tableView moveRowFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { NSArray
}
i have a coreDataStack class that takes care of all the core data stuff (creating a singelton), it looks like this:
#import "CoreDataStack.h"
@implementation CoreDataStack
#pragma mark - Core Data stack
@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
+ (instancetype)defaultStack {
static CoreDataStack *defaultStack;
static dispatch_once_t onceTocken;
dispatch_once (&onceTocken, ^{
defaultStack = [[self alloc] init];
});
return defaultStack;
}
- (NSURL *)applicationDocumentsDirectory {
// The directory the application uses to store the Core Data store file. This code uses a directory named "digitalCrown.Lister" in the application's documents directory.
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
- (NSManagedObjectModel *)managedObjectModel {
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Lister" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Create the coordinator and store
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Lister.sqlite"];
NSError *error = nil;
NSString *failureReason = @"There was an error creating or loading the application's saved data.";
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
#pragma mark - Core Data Saving support
- (void)saveContext {
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
NSError *error = nil;
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}
@end
and whenever i add new object to core data i do it this way:
- (void)insertTeget {
CoreDataStack *stack = [CoreDataStack defaultStack];
Target *target = [NSEntityDescription insertNewObjectForEntityForName:@"Target" inManagedObjectContext:stack.managedObjectContext];
if (self.myTextView.text != nil) {
target.body = self.myTextView.text;
target.time = [NSDate date];
}
[stack saveContext];
}
in the table view when I'm fetching the data i do it this way:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"StackTableViewCell";
Target *target = [self.fetchedResultController objectAtIndexPath:indexPath];
StackTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"StackTableViewCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
cell.cellLabel.text = target.body;
cell.cellLabel.font = [UIFont fontWithName:@"Candara-Bold" size:20];
cell.showsReorderControl = YES;
// Configure the cell...
return cell;
}
this is my fetchresultconroller/fetch request config in the table view controller class:
- (NSFetchRequest *)targetsFetchRequest {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Target"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"time" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
return fetchRequest;
}
- (NSFetchedResultsController *)fetchedResultController {
if (_fetchedResultController != nil) {
return _fetchedResultController;
}
CoreDataStack *stack = [CoreDataStack defaultStack];
NSFetchRequest *fetchRequest = [self targetsFetchRequest];
_fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:stack.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultController.delegate = self;
return _fetchedResultController;
}
What I want to accomplish is whenever a user create a target object it will go to the end of the array (so it will be like a queue), and if a user move cells, so I need to change the order of array of the database...
moving cells method:
- (void)moveTableView:(FMMoveTableView *)tableView moveRowFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
int start = 0;
int end = 0;
if (fromIndexPath.row > toIndexPath.row) {
start = (int)fromIndexPath.row;
end = (int)toIndexPath.row;
} else {
start = (int)toIndexPath.row;
end = (int)fromIndexPath.row;
}
for (int i = start; i <= end; ++i) {
Target *target = [self.fetchedResultController objectAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
[target setOrder:@(i)];
}
[[CoreDataStack defaultStack] saveContext];
// a test to see if the order is changed
[self.fetchedResultController performFetch:nil];
NSArray *arr = [self.fetchedResultController fetchedObjects];
for (int i=0; i<arr.count; i++) {
Target *ta = [arr objectAtIndex:i];
NSLog(@"%@",ta.body);
}
}
the log:
2015-04-14 10:29:13.405 Lister[3163:477453] One
2015-04-14 10:29:13.406 Lister[3163:477453] Two
2015-04-14 10:29:13.406 Lister[3163:477453] Three
2015-04-14 10:29:13.407 Lister[3163:477453] Four
2015-04-14 10:29:13.407 Lister[3163:477453] Five
2015-04-14 10:29:21.070 Lister[3163:477453]
2015-04-14 10:29:21.071 Lister[3163:477453] One
2015-04-14 10:29:21.071 Lister[3163:477453] Two
2015-04-14 10:29:21.071 Lister[3163:477453] Three
2015-04-14 10:29:21.072 Lister[3163:477453] Four
2015-04-14 10:29:21.072 Lister[3163:477453] Five
2015-04-14 10:29:25.037 Lister[3163:477453]
2015-04-14 10:29:25.039 Lister[3163:477453] One
2015-04-14 10:29:25.039 Lister[3163:477453] Two
2015-04-14 10:29:25.040 Lister[3163:477453] Three
2015-04-14 10:29:25.040 Lister[3163:477453] Four
2015-04-14 10:29:25.041 Lister[3163:477453] Five
Also , the label of the cells is acting weird now, if move the cell with the label "one" to the index of the cell with label "two", so the label of "one" is changing to "two". So i get to the situation that 2 cells have the same label.
Well the simplest solution would be
Target
entity, say it order
of type Integer32
.Creating and Inserting New Objects
Whenever you create a new Target
object, first fetch the existing objects from the database using sortDescriptor
having key @"order"
and ascending=YES
. Take the last object of this fetched array and check its order. Now in your new Target
object increment the order and insert it to the database. If the fetched array returns 0 objects, then set order=@(0)
.
- (void)insertTeget {
CoreDataStack *stack = [CoreDataStack defaultStack];
//Fetching objects from database
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Target"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"order" ascending:YES];
[fetchRequest setSortDescriptors:@[sortDescriptor]];
NSArray *existingObjects = [stack.managedObjectContext executeFetchRequest:fetchRequest error:nil];
//Creating new object
Target *target = [NSEntityDescription insertNewObjectForEntityForName:@"Target" inManagedObjectContext:stack.managedObjectContext];
if (self.myTextView.text != nil) {
target.body = self.myTextView.text;
target.order = @([(Target *)existingObjects.lastObject order].integerValue + 1);
}
[stack saveContext];
}
NSFetchedResultsController
sortDescriptor
.Taken from your code
- (NSFetchRequest *)targetsFetchRequest {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Target"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"order" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
return fetchRequest;
}
- (NSFetchedResultsController *)fetchedResultController {
if (_fetchedResultController != nil) {
return _fetchedResultController;
}
CoreDataStack *stack = [CoreDataStack defaultStack];
NSFetchRequest *fetchRequest = [self targetsFetchRequest];
_fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:stack.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultController.delegate = self;
return _fetchedResultController;
}
Rearranging cells
Now while when you rearrange cells in your table view, you just need to run a for loop and update their order. You need to only update order
of objects between the two indexPaths.
- (void)moveTableView:(FMMoveTableView *)tableView moveRowFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
int start = 0;
int end = 0;
if (fromIndexPath.row > toIndexPath.row) {
start = fromIndexPath.row;
end = toIndexPath.row;
} else {
start = toIndexPath.row;
end = fromIndexPath.row;
}
for (int i = start; i <= end; ++i) {
Target *target = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
[target setOrder:@(i)];
}
[[CoreDataStack defaultStack] saveContext];
}
Note: The above solution assumes that you have order
beginning from 0.
When you create and insert new Target
objects you need to implement NSFetchedResultsController
delegate methods to add corresponding rows for those objects. Since we have already defined sortDescriptor
, the new rows will be added at the end of the tableView
.
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}