How can I maintain display order in UITableView using Core Data?

后端 未结 4 1632
说谎
说谎 2020-12-12 18:01

I\'m having some trouble getting my Core Data entities to play nice and order when using an UITableView.

I\'ve been through a number of tutorials and other questions

4条回答
  •  我在风中等你
    2020-12-12 18:15

    Here a full solution how to manage an indexed table with core data. Your attribute is called displayOrder, I call it index. First of all, you better separate view controller and model. For this I use a model controller, which is the interface between the view and the model.

    There are 3 cases you need to manage that the user can influence via the view controller.

    1. Adding a new object
    2. Deleting an existing object
    3. Reorder objects.

    The first two cases Adding and Deleting are pretty straightforward. Delete calls a routine called renewObjectIndicesUpwardsFromIndex in order to update the indices after the deleted object.

    - (void)createObjectWithTitle:(NSString*)title {
        FFObject* object = [FFObject insertIntoContext:self.managedObjectContext];
    
        object.title = title;
        object.index = [NSNumber numberWithInteger:[self numberTotalObjects]];
        [self saveContext];
    }
    
    - (void)deleteObject:(FFObject*)anObject {
      NSInteger objectIndex = [anObject.index integerValue];
      [anObject deleteObject];
      [self renewObjectIndicesUpwardsFromIndex:objectIndex];
      [self saveContext];
    }
    
    - (void)renewObjectIndicesUpwardsFromIndex:(NSInteger)fromIndex {
      NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
      [fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]];
    
      NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index > %d)", fromIndex];
      [fetchRequest setPredicate:predicate];
    
      NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
      NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
    
      [fetchRequest setSortDescriptors:sortDescriptors];
    
      NSError* fetchError = nil;
      NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
    
      NSInteger index = fromIndex;
      for (FFObject* object in objects) {
        object.index = [NSNumber numberWithInteger:index];
        index += 1;
      }
      [self saveContext];
    }
    

    Before I come to the controller routines for the re-order, here the part in the view controller. I use a bool isModifyingOrder similar to this answer. Notice that the view controller calls two functions in the controller moveObjectOrderUp and moveObjectOrderDown. Depending on how you display the objects in the table view - newest first or newest last - you can switch them.

    - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
    
      isModifyingOrder = YES;
    
      NSUInteger fromIndex = sourceIndexPath.row;
      NSUInteger toIndex = destinationIndexPath.row;
    
      if (fromIndex == toIndex) {
        return;
      }
    
      FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex];
    
      NSInteger delta;
      if (fromIndex < toIndex) {
        delta = toIndex - fromIndex;
        NSLog(@"Moved down by %lu cells", delta);
        [self.objectController moveObjectOrderUp:affectedObject by:delta];
      } else {
        delta = fromIndex - toIndex;
        NSLog(@"Moved up by %lu cells", delta);
        [self.objectController moveObjectOrderDown:affectedObject by:delta];
      }
    
      isModifyingOrder = NO;
    }
    

    And here the part in the controller. This can be written nicer, but for understanding this is maybe best.

    - (void)moveObjectOrderUp:(FFObject*)affectedObject by:(NSInteger)delta {
      NSInteger fromIndex = [affectedObject.index integerValue] - delta;
      NSInteger toIndex = [affectedObject.index integerValue];
    
      if (fromIndex < 1) {
        return;
      }
    
      NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
      [fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]];
    
      NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index >= %d) AND (index < %d)", fromIndex, toIndex];
      [fetchRequest setPredicate:predicate];
    
      NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
      NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
    
      [fetchRequest setSortDescriptors:sortDescriptors];
    
      NSError* fetchError = nil;
      NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
    
      for (FFObject* object in objects) {
        NSInteger newIndex = [object.index integerValue] + 1;
        object.index = [NSNumber numberWithInteger:newIndex];
      }
    
      affectedObject.index = [NSNumber numberWithInteger:fromIndex];
    
      [self saveContext];
    }
    
    - (void)moveObjectOrderDown:(FFObject*)affectedObject by:(NSInteger)delta {
      NSInteger fromIndex = [affectedObject.index integerValue];
      NSInteger toIndex = [affectedObject.index integerValue] + delta;
    
      NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
      [fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]];
    
      NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index > %d) AND (index <= %d)", fromIndex, toIndex];
      [fetchRequest setPredicate:predicate];
    
      NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
      NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
    
      [fetchRequest setSortDescriptors:sortDescriptors];
    
      NSError* fetchError = nil;
      NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
    
      for (FFObject* object in objects)
      {
        NSInteger newIndex = [object.index integerValue] - 1;
        object.index = [NSNumber numberWithInteger:newIndex];
      }
    
      affectedObject.index = [NSNumber numberWithInteger:toIndex];
    
      [self saveContext];
    }
    

    Don't forget to use a second BOOL in your view controller for the delete action to prevent the move notification to do anything. I call it isDeleting and put it here.

    - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
      if (isModifyingOrder) return;
    
      ...
    
      switch(type) {
    
        ...
    
        case NSFetchedResultsChangeMove:
            if (isDeleting == false) {
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:localIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:localNewIndexPath]withRowAnimation:UITableViewRowAnimationFade];
            }
            break;
    
        ...
    
      }
    }
    

提交回复
热议问题