【问题标题】:How can I maintain display order in UITableView using Core Data?如何使用 Core Data 在 UITableView 中维护显示顺序?
【发布时间】:2010-12-11 12:25:45
【问题描述】:

在使用 UITableView 时,我无法让我的 Core Data 实体正常运行和有序。

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

我有一个核心数据实体,它上面有一个名为“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 显示更新正常,我可以通过我的日志消息看到 displayOrder 属性正在更新。它只是不能始终如一地保存和重新加载,而且这种实现感觉很“不正常”(除了我所有 FFObjects 的浪费迭代)。

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

【问题讨论】:

    标签: iphone uitableview sorting core-data


    【解决方案1】:

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

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

    1. 添加新对象
    2. 删除现有对象
    3. 重新排序对象。

    前两种情况的添加和删除非常简单。 Delete 调用一个名为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 的布尔值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;
    
        ...
    
      }
    }
    

    【讨论】:

      【解决方案2】:

      我认为:

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

      【讨论】:

        【解决方案3】:

        我查看了您的代码,这可能会更好:

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

        【讨论】:

        • 太棒了!这非常有效。我一直盯着原始代码太久了 - 谢谢你的帮助。
        • 这应该与stackoverflow.com/questions/1077568/…并行实现
        • 如果项目可以删除,这不起作用。您必须确保在删除行时也更新订单字段。
        • 还有另一个错误,如果 fromIndex 和 toIndex 都 = 0(如果您开始移动但将其移回),您的应用程序将崩溃 (end = 4294967295)。您应该添加一个检查两者是否相同。
        • @StephanZehrer 你真的看到这种情况发生了吗?我很惊讶在这种情况下甚至调用了这个方法,而且之前没有人报告过它。尽管如此,我还是为此案添加了支票和提前退货。看起来还好吗?
        【解决方案4】:

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

        gerry3 的小改进 - 非常优雅 - 解决方案。如果我没记错的话,那一行

        otherObject.displayOrderValue += delta;
        
        如果displayOrderValue 不是原始类型,

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

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

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

        【讨论】:

        • displayOrder 是一个核心数据实体属性(因此,NSNumber) - 我正在使用 mogenerator,它会自动创建以“Value”为后缀的原始值访问器,因此在这种情况下,指针算法非常好。感谢您指出陷阱;)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-08-27
        • 1970-01-01
        • 1970-01-01
        • 2012-11-30
        • 1970-01-01
        • 1970-01-01
        • 2012-04-12
        相关资源
        最近更新 更多