如何使用Core Data在UITableView中维护显示顺序?

时间:2009-10-30 05:54:50

标签: iphone uitableview sorting core-data

我在使用UITableView时让我的Core Data实体玩得很好并且订购时遇到了一些麻烦。

我在StackOverflow上已经完成了一些教程和其他问题,但似乎没有明确或优雅的方法来做到这一点 - 我真的希望我错过了一些东西。

我有一个Core Data实体,它上面有一个名为“displayOrder”的int16属性。我使用已经在“displayOrder”上排序的NSFetchRequest来返回我的UITableView的数据。除了重新排序之外的一切都得到了尊重。这是我的(低效)moveRowAtIndePath方法:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {         

    NSUInteger fromIndex = fromIndexPath.row;  
    NSUInteger toIndex = toIndexPath.row;

    FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex];  
    affectedObject.displayOrderValue = toIndex;

    [self FF_fetchResults];


    for (NSUInteger i = 0; i < [self.fetchedResultsController.fetchedObjects count]; i++) {  
        FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];  
        NSLog(@"Updated %@ / %@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, i);  
        otherObject.displayOrderValue = i;  
    }

    [self FF_fetchResults];  
}

有人能指出我的示例代码的方向,或者看看我做错了什么? tableview显示更新OK,我可以通过我的日志消息看到displayOrder属性正在更新。它不是一直保存和重新加载,并且对于这个实现感觉非常“偏离”(除了我所有FFObjects的浪费迭代)。

提前感谢您提供的任何建议。

4 个答案:

答案 0 :(得分:27)

我看了一下你的代码,这可能会更好:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {         

    NSUInteger fromIndex = fromIndexPath.row;  
    NSUInteger toIndex = toIndexPath.row;

    if (fromIndex == toIndex) {
        return;
    }

    FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex];  
    affectedObject.displayOrderValue = toIndex;

    NSUInteger start, end;
    int delta;

    if (fromIndex < toIndex) {
        // move was down, need to shift up
        delta = -1;
        start = fromIndex + 1;
        end = toIndex;
    } else { // fromIndex > toIndex
        // move was up, need to shift down
        delta = 1;
        start = toIndex;
        end = fromIndex - 1;
    }

    for (NSUInteger i = start; i <= end; i++) {
        FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];  
        NSLog(@"Updated %@ / %@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, otherObject.displayOrderValue + delta);  
        otherObject.displayOrderValue += delta;
    }

    [self FF_fetchResults];  
}

答案 1 :(得分:2)

(这是对gerry3上面回答的评论,但我还不能评论其他用户的问题和答案。)

gerry3的一个小改进 - 非常优雅 - 解决方案。如果我没有弄错,那就行了

otherObject.displayOrderValue += delta;
如果displayOrderValue不是基本类型,

将实际执行指针算术。这可能不是你想要的。相反,为了设置实体的价值,我建议:

otherObject.displayOrderValue = [NSNumber numberWithInt:[otherObject.displayOrderValue intValue] + delta];

这应该正确更新您的实体属性并避免任何EXC_BAD_ACCESS崩溃。

答案 2 :(得分:1)

这是一个完整的解决方案,如何使用核心数据管理索引表。您的属性名为displayOrder,我称之为index。 首先,您最好将视图控制器和模型分开。为此,我使用模型控制器,它是视图和模型之间的接口。

您需要管理3种情况,用户可以通过视图控制器进行影响。

  1. 添加新对象
  2. 删除现有对象
  3. 重新排序对象。
  4. 前两个案例添加和删除非常简单。删除调用名为renewObjectIndicesUpwardsFromIndex的例程,以便在删除的对象之后更新索引。

    - (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];
    }
    

    在我来到重新订购的控制器例程之前,这里是视图控制器中的部分。我使用类似于this answer的bool isModifyingOrder。请注意,视图控制器调用控制器moveObjectOrderUpmoveObjectOrderDown中的两个函数。根据您在表格视图中显示对象的方式 - 最新的或最新的 - 您可以切换它们。

    - (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;
    }
    

    这里是控制器中的部分。这可以写得更好,但为了理解这可能是最好的。

    - (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];
    }
    

    不要忘记在视图控制器中使用第二个BOOL进行删除操作,以防止移动通知执行任何操作。我将其称为isDeleting并将其放在此处。

    - (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;
    
        ...
    
      }
    }
    

答案 3 :(得分:0)

我认为:

    affectedObject.displayOrderValue = toIndex;

必须放在:

之后
   for (NSUInteger i = start; i <= end; i++) {
    FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];  
    NSLog(@"Updated %@ / %@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, otherObject.displayOrderValue + delta);  
    otherObject.displayOrderValue += delta;
}

之前:

    [self FF_fetchResults];